From b7583dc0b97406e7d73541f2ef362ed8b372385d Mon Sep 17 00:00:00 2001 From: dragonmacher <48328597+dragonmacher@users.noreply.github.com> Date: Tue, 11 Jul 2023 14:09:56 -0400 Subject: [PATCH] GP-3569 - Cleanup of Extension management --- .../help/topics/Debugger/GettingStarted.html | 2 +- .../Extensions/MachineLearning/lib/README.txt | 3 - .../help/topics/FrontEndPlugin/Extensions.htm | 211 +++- .../TranslateStringsPlugin.htm | 2 +- .../Base/src/main/java/ghidra/GhidraRun.java | 4 +- .../java/ghidra/util/GhidraJarBuilder.java | 2 +- .../AnalyzeAllOpenProgramsTaskTest.java | 2 +- .../AbstractGhidraScriptMgrPluginTest.java | 11 +- .../util/extensions/ExtensionUtilsTest.java | 349 ------ .../DecompilePlugin/DecompilerIntro.html | 2 +- .../VTMatchApplyFunctionSignatureTest.java | 141 +-- .../help/topics/Theming/ThemingUserDocs.html | 2 +- .../generic/test/AbstractGenericTest.java | 4 +- .../test/ConcurrentTestExceptionHandler.java | 2 +- .../java/ghidra/framework/Application.java | 2 +- .../ghidra/util/classfinder/ClassJar.java | 2 +- .../util/classfinder/ClassLocation.java | 34 +- .../ghidra/util/classfinder/ClassPackage.java | 7 +- .../src/main/resources/generic.log4jdev.xml | 15 +- .../src/main/resources/generic.log4jtest.xml | 8 +- .../ApplicationLevelPluginsConfiguration.java | 2 +- .../ghidra/framework/main/FrontEndTool.java | 4 +- .../ghidra/framework/model/ToolServices.java | 13 - .../framework/plugintool/ModalPluginTool.java | 1 - .../framework/plugintool/PluginTool.java | 4 +- .../{util => }/PluginsConfiguration.java | 19 +- .../plugintool/StandAlonePluginTool.java | 1 - .../plugintool/ToolServicesAdapter.java | 5 - .../dialog/AbstractDetailsPanel.java | 4 +- .../plugintool/dialog/ExtensionDetails.java | 208 ---- .../plugintool/dialog/ExtensionException.java | 70 -- .../plugintool/dialog/ExtensionUtils.java | 1077 ----------------- .../dialog/PluginInstallerTableModel.java | 57 +- .../util/DefaultPluginsConfiguration.java | 1 + .../plugintool/util/PluginDescription.java | 9 +- .../plugintool/util/PluginUtils.java | 131 -- .../project/extensions/ExtensionDetails.java | 366 ++++++ .../extensions}/ExtensionDetailsPanel.java | 3 +- .../extensions}/ExtensionTableModel.java | 114 +- .../extensions}/ExtensionTablePanel.java | 5 +- .../extensions}/ExtensionTableProvider.java | 119 +- .../project/extensions/ExtensionUtils.java | 953 +++++++++++++++ .../project/tool/ExtensionManager.java | 320 +++++ .../tool/GhidraPluginsConfiguration.java | 2 +- .../framework/project/tool/GhidraTool.java | 224 +--- .../project/tool/ToolServicesImpl.java | 22 - .../framework/project/tool/WorkspaceImpl.java | 44 +- .../framework/plugintool/DummyPluginTool.java | 2 - .../extensions/ExtensionUtilsTest.java | 747 ++++++++++++ .../processors/sleigh/SpecExtensionPanel.java | 6 +- .../src/test/java/ghidra/test/DummyTool.java | 8 +- .../main/java/generic/jar/ResourceFile.java | 18 +- .../java/ghidra/GhidraApplicationLayout.java | 53 +- .../src/main/java/ghidra/GhidraLauncher.java | 43 +- .../java/utilities/util/FileUtilities.java | 64 +- .../application/ApplicationLayout.java | 18 +- .../java/utility/module/ModuleUtilities.java | 41 +- .../Common/support/buildExtension.gradle | 8 +- .../screenshot/FrontEndPluginScreenShots.java | 1 + .../Introduction_to_Ghidra_Student_Guide.html | 4 +- GhidraDocs/InstallationGuide.html | 2 +- 61 files changed, 3058 insertions(+), 2540 deletions(-) delete mode 100644 Ghidra/Extensions/MachineLearning/lib/README.txt delete mode 100644 Ghidra/Features/Base/src/test/java/ghidra/util/extensions/ExtensionUtilsTest.java rename Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/{util => }/PluginsConfiguration.java (95%) delete mode 100644 Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/dialog/ExtensionDetails.java delete mode 100644 Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/dialog/ExtensionException.java delete mode 100644 Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/dialog/ExtensionUtils.java create mode 100644 Ghidra/Framework/Project/src/main/java/ghidra/framework/project/extensions/ExtensionDetails.java rename Ghidra/Framework/Project/src/main/java/ghidra/framework/{plugintool/dialog => project/extensions}/ExtensionDetailsPanel.java (97%) rename Ghidra/Framework/Project/src/main/java/ghidra/framework/{plugintool/dialog => project/extensions}/ExtensionTableModel.java (77%) rename Ghidra/Framework/Project/src/main/java/ghidra/framework/{plugintool/dialog => project/extensions}/ExtensionTablePanel.java (96%) rename Ghidra/Framework/Project/src/main/java/ghidra/framework/{plugintool/dialog => project/extensions}/ExtensionTableProvider.java (64%) create mode 100644 Ghidra/Framework/Project/src/main/java/ghidra/framework/project/extensions/ExtensionUtils.java create mode 100644 Ghidra/Framework/Project/src/main/java/ghidra/framework/project/tool/ExtensionManager.java create mode 100644 Ghidra/Framework/Project/src/test/java/ghidra/framework/project/extensions/ExtensionUtilsTest.java diff --git a/Ghidra/Debug/Debugger/src/main/help/help/topics/Debugger/GettingStarted.html b/Ghidra/Debug/Debugger/src/main/help/help/topics/Debugger/GettingStarted.html index 5c21d95efa..cacfe49fff 100644 --- a/Ghidra/Debug/Debugger/src/main/help/help/topics/Debugger/GettingStarted.html +++ b/Ghidra/Debug/Debugger/src/main/help/help/topics/Debugger/GettingStarted.html @@ -53,7 +53,7 @@

The default tool is pre-configured with a collection of plugins relevant for the Listing and for Debugger-related operations. As always, there is some chance that the tool will launch with some portion of the plugins not displayed or with a less-than-optimal layout. To verify which - plugins you have, you can select File → Configure.... "Debugger" + plugins you have, you can select File → Configure. "Debugger" should already be selected. Choosing "Configure All Plugins" (the plug icon near the top right), should show the full list of pre-selected plugins. Debugger-related plugins all begin with "Debugger". At a bare minimum, you will need the "DebuggerTargetsPlugin" and the diff --git a/Ghidra/Extensions/MachineLearning/lib/README.txt b/Ghidra/Extensions/MachineLearning/lib/README.txt deleted file mode 100644 index 1f96701da7..0000000000 --- a/Ghidra/Extensions/MachineLearning/lib/README.txt +++ /dev/null @@ -1,3 +0,0 @@ -The "lib" directory is intended to hold Jar files which this contrib -is dependent upon. This directory may be eliminated from a specific -contrib if no other Jar files are needed. diff --git a/Ghidra/Features/Base/src/main/help/help/topics/FrontEndPlugin/Extensions.htm b/Ghidra/Features/Base/src/main/help/help/topics/FrontEndPlugin/Extensions.htm index 72b5cfd911..43863e93b7 100644 --- a/Ghidra/Features/Base/src/main/help/help/topics/FrontEndPlugin/Extensions.htm +++ b/Ghidra/Features/Base/src/main/help/help/topics/FrontEndPlugin/Extensions.htm @@ -1,71 +1,156 @@ - - - - Extension Installation - - - + - + + + Extension Installation + + + -

- Ghidra Extensions

- -

Ghidra Extensions (formerly 'contribs') are Ghidra software modules that are included with a Ghidra release but not -installed by default. Ghidra Extensions can be installed and uninstalled by Ghidra at runtime, with the changes taking -effect when Ghidra is restarted. This dialog can be opened by selecting the Extensions -option on the project file menu.

+ +

Ghidra Extensions

-

-

- - - - -
-
-

+

Ghidra Extensions are Ghidra software modules that can be installed + into a Ghidra distribution. This allows users to create and share new plugins and scripts. + Ghidra ships with some pre-built extensions that not installed by default. +

+

Ghidra Extensions can be installed and uninstalled at runtime, with the changes taking effect + when Ghidra is restarted. The extension installation dialog can + be opened by selecting the Install Extensions option on the project File menu.

-

Dialog Components

-

Extensions List

-
-The list of extensions is populated when the dialog is launched. To build the list, Ghidra looks in several locations: - -

Note: Extensions that have been installed directly into the Ghidra installation directory cannot be uninstalled -from this dialog. They must be manually removed from the filesystem.

-
+
+
+ + + + +
+
+
+
+
+ -

Description Panel

-
-Displays metadata about the extension selected in the Extensions List. The information displayed is extracted from the extensions.properties file associated -with the extension. +

Dialog Components

-

extension.properties

-

The existence of this file is what tells Ghidra that the folder or zip file is a Ghidra Extension. It is a simple property file that can contain the following 4 attributes:

- -
+

Extensions Table

-

Tools Panel

-
- -
+
+

The list of extensions is populated when the dialog is launched. To build the list, Ghidra + looks in several locations:

-

Related Topics:

- - + + +
+

The color red is used in the table + to indicate that the extension version does not match the Ghidra version.

+
+ + +

Note: Extensions that have been installed directly into the Ghidra installation + directory cannot be uninstalled from this dialog. They must be manually removed from the + filesystem.

+
+ +

Description Panel

+ +
+

Displays metadata about the extension selected in the Extensions List. The information + displayed is extracted from the extensions.properties file associated with the + extension.

+ +

The existence of this file is what tells Ghidra that the folder or zip file is a Ghidra + Extension. It is a simple property file that can contain the following attributes:

+ + +
+ +

Tools Panel

+ +
+ +
+ + +

Building Extensions

+
+

+ An extension is simply a Ghidra module that contains an extension.properties file. + Building an extension is very similar to building a ghidra module, which is done by using + gradle. +

+

+ Ghidra includes a Skeleton module in the distribution that is meant to be used as + a template when creating extensions. This module can be found at +

+
+

+ <GHIDRA_INSTALL_DIR>/Extensions/Ghidra +

+
+

+ Copy and rename this directory to get started writing your own module. You can then use + gradle to build the extension by running this command from within your extension + directory: +

+
+

+ gradle -PGHIDRA_INSTALL_DIR=/path/to/ghidra/ghidra_<version>/ buildExtension +

+
+
+
+
+
+ + +

Related Topics:

+ + +
+
+
+ + + diff --git a/Ghidra/Features/Base/src/main/help/help/topics/TranslateStringsPlugin/TranslateStringsPlugin.htm b/Ghidra/Features/Base/src/main/help/help/topics/TranslateStringsPlugin/TranslateStringsPlugin.htm index a09edb9680..fece73b362 100644 --- a/Ghidra/Features/Base/src/main/help/help/topics/TranslateStringsPlugin/TranslateStringsPlugin.htm +++ b/Ghidra/Features/Base/src/main/help/help/topics/TranslateStringsPlugin/TranslateStringsPlugin.htm @@ -19,7 +19,7 @@

This plugin doesn't perform any natural language translation by itself. The user must install string translation services that do the actual translation. Extensions to Ghidra are installed via the File - -> Install Extensions... + -> Install Extensions menu.

When a string has been translated, the translated value will be shown in place of diff --git a/Ghidra/Features/Base/src/main/java/ghidra/GhidraRun.java b/Ghidra/Features/Base/src/main/java/ghidra/GhidraRun.java index 762b6ce7d0..33d25f5194 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/GhidraRun.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/GhidraRun.java @@ -30,8 +30,8 @@ import ghidra.framework.client.RepositoryAdapter; import ghidra.framework.data.DomainObjectAdapter; import ghidra.framework.main.FrontEndTool; import ghidra.framework.model.*; -import ghidra.framework.plugintool.dialog.ExtensionUtils; import ghidra.framework.project.DefaultProjectManager; +import ghidra.framework.project.extensions.ExtensionUtils; import ghidra.framework.store.LockException; import ghidra.program.database.ProgramDB; import ghidra.util.*; @@ -81,7 +81,7 @@ public class GhidraRun implements GhidraLaunchable { updateSplashScreenStatusMessage("Populating Ghidra help..."); GhidraHelpService.install(); - ExtensionUtils.cleanupUninstalledExtensions(); + ExtensionUtils.initializeExtensions(); // Allows handling of old content which did not have a content type property DomainObjectAdapter.setDefaultContentClass(ProgramDB.class); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/util/GhidraJarBuilder.java b/Ghidra/Features/Base/src/main/java/ghidra/util/GhidraJarBuilder.java index 59d04bca61..197e098a5f 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/util/GhidraJarBuilder.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/util/GhidraJarBuilder.java @@ -26,7 +26,7 @@ import generic.jar.*; import ghidra.GhidraApplicationLayout; import ghidra.GhidraLaunchable; import ghidra.framework.*; -import ghidra.framework.plugintool.dialog.ExtensionUtils; +import ghidra.framework.project.extensions.ExtensionUtils; import ghidra.util.classfinder.ClassFinder; import ghidra.util.classfinder.ClassSearcher; import ghidra.util.exception.AssertException; diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/analysis/AnalyzeAllOpenProgramsTaskTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/analysis/AnalyzeAllOpenProgramsTaskTest.java index 0b7ea98512..343968bacc 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/analysis/AnalyzeAllOpenProgramsTaskTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/analysis/AnalyzeAllOpenProgramsTaskTest.java @@ -30,7 +30,7 @@ import ghidra.framework.model.Project; import ghidra.framework.options.Options; import ghidra.framework.options.ToolOptions; import ghidra.framework.plugintool.PluginTool; -import ghidra.framework.plugintool.util.PluginsConfiguration; +import ghidra.framework.plugintool.PluginsConfiguration; import ghidra.program.database.ProgramBuilder; import ghidra.program.model.listing.Program; import ghidra.test.AbstractGhidraHeadedIntegrationTest; diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/AbstractGhidraScriptMgrPluginTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/AbstractGhidraScriptMgrPluginTest.java index 46893f5f64..e64e5855c0 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/AbstractGhidraScriptMgrPluginTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/AbstractGhidraScriptMgrPluginTest.java @@ -182,9 +182,8 @@ public abstract class AbstractGhidraScriptMgrPluginTest } protected void deleteUserScripts() throws IOException { - Path userScriptDir = Paths.get(GhidraScriptUtil.USER_SCRIPTS_DIR); - FileUtilities.forEachFile(userScriptDir, paths -> paths.forEach(p -> delete(p))); + FileUtilities.forEachFile(userScriptDir, script -> delete(script)); } //================================================================================================== @@ -988,10 +987,10 @@ public abstract class AbstractGhidraScriptMgrPluginTest // destroy any NewScriptxxx files...and Temp ones too List paths = provider.getBundleHost() - .getBundleFiles() - .stream() - .filter(ResourceFile::isDirectory) - .collect(Collectors.toList()); + .getBundleFiles() + .stream() + .filter(ResourceFile::isDirectory) + .collect(Collectors.toList()); for (ResourceFile path : paths) { File file = path.getFile(false); diff --git a/Ghidra/Features/Base/src/test/java/ghidra/util/extensions/ExtensionUtilsTest.java b/Ghidra/Features/Base/src/test/java/ghidra/util/extensions/ExtensionUtilsTest.java deleted file mode 100644 index 90fd391712..0000000000 --- a/Ghidra/Features/Base/src/test/java/ghidra/util/extensions/ExtensionUtilsTest.java +++ /dev/null @@ -1,349 +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.util.extensions; - -import static org.junit.Assert.*; - -import java.io.*; -import java.util.Set; -import java.util.zip.ZipEntry; -import java.util.zip.ZipOutputStream; - -import org.junit.Before; -import org.junit.Test; - -import docking.test.AbstractDockingTest; -import generic.jar.ResourceFile; -import ghidra.framework.Application; -import ghidra.framework.plugintool.dialog.*; -import utilities.util.FileUtilities; -import utility.application.ApplicationLayout; - -/** - * Tests for the {@link ExtensionUtils} class. - * - */ -public class ExtensionUtilsTest extends AbstractDockingTest { - - // Name used in all tests when creating extensions. - private String DEFAULT_EXT_NAME = "test"; - - private ApplicationLayout gLayout; - - /* - * Create dummy archive and installation folders in the temp space that we can populate - * with extensions. - */ - @Before - public void setup() throws IOException { - - gLayout = Application.getApplicationLayout(); - - // Verify that the archive and install directories are empty (each test requires - // we start with a clean slate). If they're not empty, CORRECT THE SITUATION. - if (!checkCleanInstall()) { - FileUtilities.deleteDir(gLayout.getExtensionArchiveDir().getFile(false)); - for (ResourceFile installDir : gLayout.getExtensionInstallationDirs()) { - FileUtilities.deleteDir(installDir.getFile(false)); - } - } - - createExtensionDirs(); - } - - /* - * Verifies that we can install an extension from a .zip file. - */ - @Test - public void testInstallExtensionFromZip() throws IOException { - - // Create an extension and install it. - ResourceFile rFile = new ResourceFile(createExtensionZip(DEFAULT_EXT_NAME)); - ExtensionUtils.install(rFile); - - // Verify there is something in the installation directory and it has the correct name - checkDirtyInstall(DEFAULT_EXT_NAME); - } - - /* - * Verifies that we can install an extension from a folder. - */ - @Test - public void testInstallExtensionFromFolder() throws IOException { - - // Create an extension and install it. - ResourceFile rFile = createExtensionFolder(); - ExtensionUtils.install(rFile); - - // Verify the extension is in the install folder and has the correct name - checkDirtyInstall(DEFAULT_EXT_NAME); - } - - /* - * Verifies that we can uninstall an extension. - */ - @Test - public void testUninstallExtension() throws ExtensionException, IOException { - - // Create an extension and install it. - ResourceFile rFile = new ResourceFile(createExtensionZip(DEFAULT_EXT_NAME)); - ExtensionUtils.install(rFile); - - checkDirtyInstall(DEFAULT_EXT_NAME); - - // Get the extension object that we need to uninstall - there will only - // be one in the set. - Set extensions = ExtensionUtils.getExtensions(); - assertTrue(extensions.size() == 1); - - ExtensionDetails ext = extensions.iterator().next(); - - // Now uninstall it and verify we have a clean install folder - ExtensionUtils.uninstall(ext); - - checkCleanInstall(); - } - - /* - * Verifies that trying to install an extension when there's already one with the same - * name installed will overwrite the existing and not throw an exception - * - * @throws Exception if there's a problem creating the temp extension folder - */ - @Test - public void testInstallExtensionDuplicate() throws Exception { - - // Create an extension and install it. - ResourceFile rFile = createExtensionFolder(); - ExtensionUtils.install(rFile); - - // Now create another extension with the same name and try - // to install it. - rFile = new ResourceFile(createExtensionZip(DEFAULT_EXT_NAME)); - - boolean install = ExtensionUtils.install(rFile); - assertEquals(install, true); - } - - /* - * Verifies that we can properly recognize a valid .zip file. - */ - @Test - public void testIsZip() throws IOException, ExtensionException { - File zipFile = createExtensionZip(DEFAULT_EXT_NAME); - assertTrue(ExtensionUtils.isZip(zipFile)); - } - - /* - * Verifies that we can identify when a .zip is a valid extension archive vs. - * just a regular old zip (ROZ). - *

- * Note: The presence of an extensions.properties file is the difference. - */ - @Test - public void testIsExtension_Zip() throws IOException, ExtensionException { - File zipFile1 = createExtensionZip(DEFAULT_EXT_NAME); - assertTrue(ExtensionUtils.isExtension(new ResourceFile(zipFile1))); - - File zipFile2 = createNonExtensionZip(DEFAULT_EXT_NAME); - assertTrue(!ExtensionUtils.isExtension(new ResourceFile(zipFile2))); - } - - /* - * Verifies that we can recognize when a directory represents an extension. - *

- * Note: The presence of an extensions.properties file is the difference. - */ - @Test - public void testIsExtension_Folder() throws IOException, ExtensionException { - File extDir = createTempDirectory("TestExtFolder"); - new File(extDir, "extension.properties").createNewFile(); - assertTrue(ExtensionUtils.isExtension(new ResourceFile(extDir))); - - File nonExtDir = createTempDirectory("TestNonExtFolder"); - assertTrue(!ExtensionUtils.isExtension(new ResourceFile(nonExtDir))); - } - - /* - * Verifies that the we can retrieve all unique extensions in the archive and - * install folders. - *

- * Note: This test eliminates the need to test the methods for retrieving archived vs. installed - * extensions individually. - */ - @Test - public void testGetExtensions() throws ExtensionException, IOException { - - // First create an extension and install it, so we have 2 extensions: one in - // the archive folder, and one in the install folder. - File zipFile = createExtensionZip(DEFAULT_EXT_NAME); - ExtensionUtils.install(new ResourceFile(zipFile)); - - // Now getExtensions should give us exactly 1 extension in the return. - Set extensions = ExtensionUtils.getExtensions(); - assertTrue(extensions.size() == 1); - - // Now add an archive extension with a different name and see if we get - // 2 total extensions. - createExtensionZip("Extension2"); - extensions = ExtensionUtils.getExtensions(); - assertTrue(extensions.size() == 2); - - // Now add a 3rd extension and install it. See if we have 3 total extensions. - File extension3 = createExtensionZip("Extension3"); - ExtensionUtils.install(new ResourceFile(extension3)); - extensions = ExtensionUtils.getExtensions(); - assertTrue(extensions.size() == 3); - } - - /* - * Catch-all test for verifying that 'bad' inputs to utility functions are - * handled properly. - */ - @Test - public void testBadInputs() { - - boolean foundError = false; - - try { - ExtensionUtils.uninstall((ExtensionDetails) null); - ExtensionUtils.isExtension(null); - ExtensionUtils.isZip(null); - ExtensionUtils.install(new ResourceFile(new File("this/file/does/not/exist"))); - ExtensionUtils.install((ResourceFile) null); - ExtensionUtils.install((ExtensionDetails) null, true); - } - catch (Exception e) { - foundError = true; - } - - assertTrue(foundError == false); - } - -//================================================================================================== -// Private Methods -//================================================================================================== - - /* - * Creates the extension archive and installation directories. - * - * @throws IOException if there's an error creating the directories - */ - private void createExtensionDirs() throws IOException { - - ResourceFile extensionDir = gLayout.getExtensionArchiveDir(); - if (!extensionDir.exists()) { - if (!extensionDir.mkdir()) { - throw new IOException("Failed to create extension archive directory for test"); - } - } - - ResourceFile installDir = gLayout.getExtensionInstallationDirs().get(0); - if (!installDir.exists()) { - if (!installDir.mkdir()) { - throw new IOException("Failed to create extension installation directory for test"); - } - } - } - - /* - * Verifies that the installation folder is empty. - */ - private boolean checkCleanInstall() { - ResourceFile[] files = gLayout.getExtensionInstallationDirs().get(0).listFiles(); - return (files == null || files.length == 0); - } - - /* - * Verifies that the installation folder is not empty and contains a folder - * with the given name. - * - * @param name the name of the installed extension - */ - private void checkDirtyInstall(String name) { - ResourceFile[] files = gLayout.getExtensionInstallationDirs().get(0).listFiles(); - assertTrue(files.length >= 1); - assertTrue(files[0].getName().equals(name)); - } - - /* - * Creates a valid extension in the archive folder. This extension is not a - * .zip, but a folder. - * - * @return the file representing the extension - * @throws IOException if there's an error creating the extension - */ - private ResourceFile createExtensionFolder() throws IOException { - - ResourceFile root = new ResourceFile(gLayout.getExtensionArchiveDir(), DEFAULT_EXT_NAME); - root.mkdir(); - - // Have to add a prop file so this will be recognized as an extension - File propFile = new ResourceFile(root, "extension.properties").getFile(false); - propFile.createNewFile(); - - return root; - } - - /* - * Create a generic zip that is a valid extension archive. - * - * @param zipName name of the zip to create - * @return a zip file - * @throws IOException if there's an error creating the zip - */ - private File createExtensionZip(String zipName) throws IOException { - - File f = new File(gLayout.getExtensionArchiveDir().getFile(false), zipName + ".zip"); - try (ZipOutputStream out = new ZipOutputStream(new FileOutputStream(f))) { - out.putNextEntry(new ZipEntry(zipName + "/")); - out.putNextEntry(new ZipEntry(zipName + "/extension.properties")); - - StringBuilder sb = new StringBuilder(); - sb.append("name:" + zipName); - byte[] data = sb.toString().getBytes(); - out.write(data, 0, data.length); - out.closeEntry(); - } - - return f; - } - - /* - * Create a generic zip that is NOT a valid extension archive (because it doesn't - * have an extension.properties file). - * - * @param zipName name of the zip to create - * @return a zip file - * @throws IOException if there's an error creating the zip - */ - private File createNonExtensionZip(String zipName) throws IOException { - - File f = new File(gLayout.getExtensionArchiveDir().getFile(false), zipName + ".zip"); - try (ZipOutputStream out = new ZipOutputStream(new FileOutputStream(f))) { - out.putNextEntry(new ZipEntry(zipName + "/")); - out.putNextEntry(new ZipEntry(zipName + "/randomFile.txt")); - - StringBuilder sb = new StringBuilder(); - sb.append("name:" + zipName); - byte[] data = sb.toString().getBytes(); - out.write(data, 0, data.length); - out.closeEntry(); - } - - return f; - } -} diff --git a/Ghidra/Features/Decompiler/src/main/help/help/topics/DecompilePlugin/DecompilerIntro.html b/Ghidra/Features/Decompiler/src/main/help/help/topics/DecompilePlugin/DecompilerIntro.html index d6d603e624..b5e97f2ed8 100644 --- a/Ghidra/Features/Decompiler/src/main/help/help/topics/DecompilePlugin/DecompilerIntro.html +++ b/Ghidra/Features/Decompiler/src/main/help/help/topics/DecompilePlugin/DecompilerIntro.html @@ -43,7 +43,7 @@ a Code Browser by selecting the

- File -> Configure... + File -> Configure

menu option, then clicking on the Configure link under the diff --git a/Ghidra/Features/VersionTracking/src/test.slow/java/ghidra/feature/vt/api/VTMatchApplyFunctionSignatureTest.java b/Ghidra/Features/VersionTracking/src/test.slow/java/ghidra/feature/vt/api/VTMatchApplyFunctionSignatureTest.java index a1ee32be54..ae0b8c6eda 100644 --- a/Ghidra/Features/VersionTracking/src/test.slow/java/ghidra/feature/vt/api/VTMatchApplyFunctionSignatureTest.java +++ b/Ghidra/Features/VersionTracking/src/test.slow/java/ghidra/feature/vt/api/VTMatchApplyFunctionSignatureTest.java @@ -15,85 +15,30 @@ */ package ghidra.feature.vt.api; -import static ghidra.feature.vt.db.VTTestUtils.addr; -import static ghidra.feature.vt.db.VTTestUtils.createMatchSetWithOneMatch; -import static ghidra.feature.vt.gui.util.VTOptionDefines.CALLING_CONVENTION; -import static ghidra.feature.vt.gui.util.VTOptionDefines.FUNCTION_RETURN_TYPE; -import static ghidra.feature.vt.gui.util.VTOptionDefines.FUNCTION_SIGNATURE; -import static ghidra.feature.vt.gui.util.VTOptionDefines.INLINE; -import static ghidra.feature.vt.gui.util.VTOptionDefines.NO_RETURN; -import static ghidra.feature.vt.gui.util.VTOptionDefines.PARAMETER_COMMENTS; -import static ghidra.feature.vt.gui.util.VTOptionDefines.PARAMETER_DATA_TYPES; -import static ghidra.feature.vt.gui.util.VTOptionDefines.PARAMETER_NAMES; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; +import static ghidra.feature.vt.db.VTTestUtils.*; +import static ghidra.feature.vt.gui.util.VTOptionDefines.*; +import static org.junit.Assert.*; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; +import java.util.*; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; +import org.junit.*; import ghidra.feature.vt.api.db.VTSessionDB; -import ghidra.feature.vt.api.main.VTAssociationStatus; -import ghidra.feature.vt.api.main.VTMarkupItem; -import ghidra.feature.vt.api.main.VTMarkupItemStatus; -import ghidra.feature.vt.api.main.VTMatch; -import ghidra.feature.vt.api.main.VTSession; +import ghidra.feature.vt.api.main.*; import ghidra.feature.vt.api.markuptype.FunctionSignatureMarkupType; -import ghidra.feature.vt.gui.plugin.VTController; -import ghidra.feature.vt.gui.plugin.VTControllerImpl; -import ghidra.feature.vt.gui.plugin.VTPlugin; -import ghidra.feature.vt.gui.task.ApplyMatchTask; -import ghidra.feature.vt.gui.task.ClearMatchTask; -import ghidra.feature.vt.gui.task.VtTask; +import ghidra.feature.vt.gui.plugin.*; +import ghidra.feature.vt.gui.task.*; import ghidra.feature.vt.gui.util.MatchInfo; -import ghidra.feature.vt.gui.util.VTMatchApplyChoices.CallingConventionChoices; -import ghidra.feature.vt.gui.util.VTMatchApplyChoices.CommentChoices; -import ghidra.feature.vt.gui.util.VTMatchApplyChoices.FunctionNameChoices; -import ghidra.feature.vt.gui.util.VTMatchApplyChoices.FunctionSignatureChoices; -import ghidra.feature.vt.gui.util.VTMatchApplyChoices.HighestSourcePriorityChoices; -import ghidra.feature.vt.gui.util.VTMatchApplyChoices.LabelChoices; -import ghidra.feature.vt.gui.util.VTMatchApplyChoices.ParameterDataTypeChoices; -import ghidra.feature.vt.gui.util.VTMatchApplyChoices.ReplaceChoices; -import ghidra.feature.vt.gui.util.VTMatchApplyChoices.SourcePriorityChoices; +import ghidra.feature.vt.gui.util.VTMatchApplyChoices.*; import ghidra.feature.vt.gui.util.VTOptionDefines; import ghidra.framework.options.ToolOptions; import ghidra.framework.plugintool.PluginTool; import ghidra.framework.store.LockException; import ghidra.program.database.ProgramBuilder; import ghidra.program.model.address.Address; -import ghidra.program.model.data.ArrayDataType; -import ghidra.program.model.data.BooleanDataType; -import ghidra.program.model.data.CategoryPath; -import ghidra.program.model.data.CharDataType; -import ghidra.program.model.data.DataType; -import ghidra.program.model.data.FloatDataType; -import ghidra.program.model.data.IntegerDataType; -import ghidra.program.model.data.Pointer; -import ghidra.program.model.data.PointerDataType; -import ghidra.program.model.data.StructureDataType; -import ghidra.program.model.data.TypeDef; -import ghidra.program.model.data.TypedefDataType; -import ghidra.program.model.data.Undefined4DataType; -import ghidra.program.model.data.VoidDataType; -import ghidra.program.model.data.WordDataType; -import ghidra.program.model.lang.CompilerSpec; -import ghidra.program.model.lang.CompilerSpecDescription; -import ghidra.program.model.lang.CompilerSpecID; -import ghidra.program.model.lang.Language; -import ghidra.program.model.lang.LanguageID; -import ghidra.program.model.lang.LanguageNotFoundException; -import ghidra.program.model.lang.LanguageService; -import ghidra.program.model.listing.Function; -import ghidra.program.model.listing.IncompatibleLanguageException; -import ghidra.program.model.listing.Parameter; -import ghidra.program.model.listing.ParameterImpl; -import ghidra.program.model.listing.Program; +import ghidra.program.model.data.*; +import ghidra.program.model.lang.*; +import ghidra.program.model.listing.*; import ghidra.program.model.symbol.SourceType; import ghidra.program.util.DefaultLanguageService; import ghidra.test.AbstractGhidraHeadedIntegrationTest; @@ -104,8 +49,6 @@ import ghidra.util.task.TaskMonitor; public class VTMatchApplyFunctionSignatureTest extends AbstractGhidraHeadedIntegrationTest { -// private static final String TEST_SOURCE_PROGRAM_NAME = "VersionTracking/WallaceSrc"; -// private static final String TEST_DESTINATION_PROGRAM_NAME = "VersionTracking/WallaceVersion2"; private TestEnv env; private PluginTool tool; private VTController controller; @@ -130,8 +73,8 @@ public class VTMatchApplyFunctionSignatureTest extends AbstractGhidraHeadedInteg public void setUp() throws Exception { env = new TestEnv(); - sourceProgram = createSourceProgram();// env.getProgram(TEST_SOURCE_PROGRAM_NAME); - destinationProgram = createDestinationProgram();// env.getProgram(TEST_DESTINATION_PROGRAM_NAME); + sourceProgram = createSourceProgram(); + destinationProgram = createDestinationProgram(); tool = env.getTool(); tool.addPlugin(VTPlugin.class.getName()); @@ -142,37 +85,15 @@ public class VTMatchApplyFunctionSignatureTest extends AbstractGhidraHeadedInteg VTSessionDB.createVTSession(testName.getMethodName() + " - Test Match Set Manager", sourceProgram, destinationProgram, this); - runSwing(new Runnable() { - @Override - public void run() { - controller.openVersionTrackingSession(session); - } - }); + runSwing(() -> controller.openVersionTrackingSession(session)); setAllOptionsToDoNothing(); -// -// env = new VTTestEnv(); -// session = env.createSession(TEST_SOURCE_PROGRAM_NAME, TEST_DESTINATION_PROGRAM_NAME); -// try { -// correlator = -// vtTestEnv.correlate(new ExactMatchInstructionsProgramCorrelatorFactory(), null, -// TaskMonitor.DUMMY); -// } -// catch (Exception e) { -// Assert.fail(e.getMessage()); -// e.printStackTrace(); -// } -// sourceProgram = env.getSourceProgram(); -// destinationProgram = env.getDestinationProgram(); -// controller = env.getVTController(); -// env.showTool(); -// // Logger functionLogger = Logger.getLogger(FunctionDB.class); -// functionLogger.setLevel(Level.TRACE); -// +// Configurator.setLevel(functionLogger.getName(), org.apache.logging.log4j.Level.TRACE); +// // Logger variableLogger = Logger.getLogger(VariableSymbolDB.class); -// variableLogger.setLevel(Level.TRACE); +// Configurator.setLevel(variableLogger.getName(), org.apache.logging.log4j.Level.TRACE); } @@ -478,7 +399,6 @@ public class VTMatchApplyFunctionSignatureTest extends AbstractGhidraHeadedInteg checkSignatures("undefined use(Gadget * this, Person * person)", "undefined FUN_00401040(void * this, undefined4 param_1)"); - tx(sourceProgram, () -> { sourceFunction.setCustomVariableStorage(true); @@ -487,7 +407,6 @@ public class VTMatchApplyFunctionSignatureTest extends AbstractGhidraHeadedInteg SourceType.USER_DEFINED); }); - DataType personType = sourceProgram.getDataTypeManager().getDataType("/Person"); assertNotNull(personType); @@ -495,7 +414,6 @@ public class VTMatchApplyFunctionSignatureTest extends AbstractGhidraHeadedInteg destinationFunction.setCustomVariableStorage(true); }); - // Set the function signature options for this test ToolOptions applyOptions = controller.getOptions(); applyOptions.setEnum(FUNCTION_SIGNATURE, FunctionSignatureChoices.REPLACE); @@ -744,24 +662,14 @@ public class VTMatchApplyFunctionSignatureTest extends AbstractGhidraHeadedInteg public void testApplyMatch_ReplaceSignatureAndCallingConventionDifferentLanguageFailUsingNameMatch() throws Exception { - runSwing(new Runnable() { - @Override - public void run() { - controller.closeCurrentSessionIgnoringChanges(); - } - }); + runSwing(() -> controller.closeCurrentSessionIgnoringChanges()); env.release(destinationProgram); destinationProgram = createToyDestinationProgram();// env.getProgram("helloProgram"); // get a program without cdecl session = VTSessionDB.createVTSession(testName.getMethodName() + " - Test Match Set Manager", sourceProgram, destinationProgram, this); - runSwing(new Runnable() { - @Override - public void run() { - controller.openVersionTrackingSession(session); - } - }); + runSwing(() -> controller.openVersionTrackingSession(session)); useMatch("0x00401040", "0x00010938"); @@ -1699,12 +1607,9 @@ public class VTMatchApplyFunctionSignatureTest extends AbstractGhidraHeadedInteg final String[] sourceStringBox = new String[1]; final String[] destinationStringBox = new String[1]; - runSwing(new Runnable() { - @Override - public void run() { - sourceStringBox[0] = sourceFunction.getPrototypeString(false, false); - destinationStringBox[0] = destinationFunction.getPrototypeString(false, false); - } + runSwing(() -> { + sourceStringBox[0] = sourceFunction.getPrototypeString(false, false); + destinationStringBox[0] = destinationFunction.getPrototypeString(false, false); }); assertEquals(expectedSourceSignature, sourceStringBox[0]); diff --git a/Ghidra/Framework/Docking/src/main/help/help/topics/Theming/ThemingUserDocs.html b/Ghidra/Framework/Docking/src/main/help/help/topics/Theming/ThemingUserDocs.html index f869df2f49..87ddf04dc6 100644 --- a/Ghidra/Framework/Docking/src/main/help/help/topics/Theming/ThemingUserDocs.html +++ b/Ghidra/Framework/Docking/src/main/help/help/topics/Theming/ThemingUserDocs.html @@ -153,7 +153,7 @@ modified using the Theme Editor Dialog. The Theme Editor Dialog can be invoked from the main application menu using the EditThemeConfigure..." menu. Choose the + src="help/shared/arrow.gif" border="0">Configure" menu. Choose the tab for the appropriate type and double-click on the ID column or Current Value column of the item you want to change. An editor for that type will appear.

diff --git a/Ghidra/Framework/Generic/src/main/java/generic/test/AbstractGenericTest.java b/Ghidra/Framework/Generic/src/main/java/generic/test/AbstractGenericTest.java index 1c240ba8fb..3177ed3612 100644 --- a/Ghidra/Framework/Generic/src/main/java/generic/test/AbstractGenericTest.java +++ b/Ghidra/Framework/Generic/src/main/java/generic/test/AbstractGenericTest.java @@ -646,11 +646,11 @@ public abstract class AbstractGenericTest extends AbstractGTest { */ public static void setErrorsExpected(boolean expected) { if (expected) { - Msg.error(AbstractGenericTest.class, ">>>>>>>>>>>>>>>> Expected Exception"); + Msg.error(AbstractGenericTest.class, ">>>>>>>>>>>>>>>> Expected Errors"); ConcurrentTestExceptionHandler.disable(); } else { - Msg.error(AbstractGenericTest.class, "<<<<<<<<<<<<<<<< End Expected Exception"); + Msg.error(AbstractGenericTest.class, "<<<<<<<<<<<<<<<< End Expected Errors"); ConcurrentTestExceptionHandler.enable(); } } diff --git a/Ghidra/Framework/Generic/src/main/java/generic/test/ConcurrentTestExceptionHandler.java b/Ghidra/Framework/Generic/src/main/java/generic/test/ConcurrentTestExceptionHandler.java index bb93909129..5d971df386 100644 --- a/Ghidra/Framework/Generic/src/main/java/generic/test/ConcurrentTestExceptionHandler.java +++ b/Ghidra/Framework/Generic/src/main/java/generic/test/ConcurrentTestExceptionHandler.java @@ -76,7 +76,7 @@ public class ConcurrentTestExceptionHandler implements UncaughtExceptionHandler * environmental issues rather than real problems. This method is intended to ignore * these less-than-serious issues. * - * @param throwable the throwable to examine + * @param t the throwable to examine * @return true if it should be ignored */ private static boolean isKnownTestMachineTimingBug(Throwable t) { diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/framework/Application.java b/Ghidra/Framework/Generic/src/main/java/ghidra/framework/Application.java index 5b3404d120..d4f06e2b0d 100644 --- a/Ghidra/Framework/Generic/src/main/java/ghidra/framework/Application.java +++ b/Ghidra/Framework/Generic/src/main/java/ghidra/framework/Application.java @@ -805,7 +805,7 @@ public class Application { */ public static Collection getLibraryDirectories() { checkAppInitialized(); - return ModuleUtilities.getModuleLibDirectories(app.layout.getModules()); + return ModuleUtilities.getModuleLibDirectories(app.layout.getModules().values()); } /** diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/util/classfinder/ClassJar.java b/Ghidra/Framework/Generic/src/main/java/ghidra/util/classfinder/ClassJar.java index c57c41bce9..26c66acdd1 100644 --- a/Ghidra/Framework/Generic/src/main/java/ghidra/util/classfinder/ClassJar.java +++ b/Ghidra/Framework/Generic/src/main/java/ghidra/util/classfinder/ClassJar.java @@ -61,7 +61,7 @@ class ClassJar extends ClassLocation { } @Override - void getClasses(Set> set, TaskMonitor monitor) { + protected void getClasses(Set> set, TaskMonitor monitor) { checkForDuplicates(set); set.addAll(classes); } diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/util/classfinder/ClassLocation.java b/Ghidra/Framework/Generic/src/main/java/ghidra/util/classfinder/ClassLocation.java index 0a7e1b3a6f..94b9f6763d 100644 --- a/Ghidra/Framework/Generic/src/main/java/ghidra/util/classfinder/ClassLocation.java +++ b/Ghidra/Framework/Generic/src/main/java/ghidra/util/classfinder/ClassLocation.java @@ -15,6 +15,7 @@ */ package ghidra.util.classfinder; +import java.net.URL; import java.util.HashSet; import java.util.Set; @@ -31,26 +32,35 @@ abstract class ClassLocation { protected static final String CLASS_EXT = ".class"; - final Logger log = LogManager.getLogger(getClass()); + protected final Logger log = LogManager.getLogger(getClass()); protected Set> classes = new HashSet<>(); - abstract void getClasses(Set> set, TaskMonitor monitor) throws CancelledException; - - void checkForDuplicates(Set> existingClasses) { - if (!log.isTraceEnabled()) { - return; - } + protected abstract void getClasses(Set> set, TaskMonitor monitor) + throws CancelledException; + protected void checkForDuplicates(Set> existingClasses) { for (Class c : classes) { + // Note: our class and a matching class in 'existingClasses' will be '==' since the + // class loader loaded the class by name--it will always find the same class, in + // classpath order. if (existingClasses.contains(c)) { - Module module = c.getModule(); - module.toString(); - log.trace("Attempting to load the same class twice: {}. " + - "Keeping loaded class ; ignoring class from {}", c, this); - return; + log.warn(() -> generateMessage(c)); } } } + private String generateMessage(Class c) { + return String.format("Class defined in multiple locations: %s. Keeping class loaded " + + "from %s; ignoring class from %s", c, toLocation(c), this); + } + + private String toLocation(Class clazz) { + String name = clazz.getName(); + String classAsPath = '/' + name.replace('.', '/') + ".class"; + URL url = clazz.getResource(classAsPath); + String urlPath = url.getPath(); + int index = urlPath.indexOf(classAsPath); + return urlPath.substring(0, index); + } } diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/util/classfinder/ClassPackage.java b/Ghidra/Framework/Generic/src/main/java/ghidra/util/classfinder/ClassPackage.java index 1c5c6d55df..4420266ddf 100644 --- a/Ghidra/Framework/Generic/src/main/java/ghidra/util/classfinder/ClassPackage.java +++ b/Ghidra/Framework/Generic/src/main/java/ghidra/util/classfinder/ClassPackage.java @@ -88,7 +88,7 @@ class ClassPackage extends ClassLocation { } @Override - void getClasses(Set> set, TaskMonitor monitor) throws CancelledException { + protected void getClasses(Set> set, TaskMonitor monitor) throws CancelledException { checkForDuplicates(set); @@ -120,4 +120,9 @@ class ClassPackage extends ClassLocation { } return results; } + + @Override + public String toString() { + return packageDir.toString(); + } } diff --git a/Ghidra/Framework/Generic/src/main/resources/generic.log4jdev.xml b/Ghidra/Framework/Generic/src/main/resources/generic.log4jdev.xml index 345de53abf..0f7596650f 100644 --- a/Ghidra/Framework/Generic/src/main/resources/generic.log4jdev.xml +++ b/Ghidra/Framework/Generic/src/main/resources/generic.log4jdev.xml @@ -44,6 +44,7 @@ +