From 359faba77aa1555d3b05518ecf8491c6e9158807 Mon Sep 17 00:00:00 2001 From: dragonmacher <48328597+dragonmacher@users.noreply.github.com> Date: Tue, 6 Feb 2024 17:01:02 -0500 Subject: [PATCH] GP-4294 - Fixed exception looking for extensions when running headlessly in fat jar mode --- .../ghidra_scripts/BuildGhidraJarScript.java | 5 +- .../app/util/headless/HeadlessAnalyzer.java | 60 ++++++++++--------- .../ghidra/GhidraJarApplicationLayout.java | 19 ++++-- 3 files changed, 49 insertions(+), 35 deletions(-) diff --git a/Ghidra/Features/Base/ghidra_scripts/BuildGhidraJarScript.java b/Ghidra/Features/Base/ghidra_scripts/BuildGhidraJarScript.java index 675904cb82..e9bd0ffd2e 100644 --- a/Ghidra/Features/Base/ghidra_scripts/BuildGhidraJarScript.java +++ b/Ghidra/Features/Base/ghidra_scripts/BuildGhidraJarScript.java @@ -62,9 +62,12 @@ public class BuildGhidraJarScript extends GhidraScript { builder.addExcludedFileExtension(".pdf"); File installDir = Application.getInstallationDirectory().getFile(true); - builder.buildJar(new File(installDir, "ghidra.jar"), null, monitor); + File file = new File(installDir, "ghidra.jar"); + builder.buildJar(file, null, monitor); // uncomment the following line to create a src zip for debugging. // builder.buildSrcZip(new File(installDir, "GhidraSrc.zip"), monitor); + + println("Finsished writing jar: " + file); } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/headless/HeadlessAnalyzer.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/headless/HeadlessAnalyzer.java index f13bab0445..d7c3a1a886 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/headless/HeadlessAnalyzer.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/headless/HeadlessAnalyzer.java @@ -59,16 +59,16 @@ import ghidra.util.task.TaskMonitor; import utilities.util.FileUtilities; /** - * The class used kick-off and interact with headless processing. All headless options have been - * broken out into their own class: {@link HeadlessOptions}. This class is intended to be used + * The class used kick-off and interact with headless processing. All headless options have been + * broken out into their own class: {@link HeadlessOptions}. This class is intended to be used * one of two ways: * *

- * Note: This class is not thread safe. + * Note: This class is not thread safe. */ public class HeadlessAnalyzer { @@ -84,16 +84,16 @@ public class HeadlessAnalyzer { private FileSystemService fsService; /** - * Gets a headless analyzer, initializing the application if necessary with the specified - * logging parameters. An {@link IllegalStateException} will be thrown if the application has + * Gets a headless analyzer, initializing the application if necessary with the specified + * logging parameters. An {@link IllegalStateException} will be thrown if the application has * already been initialized or a headless analyzer has already been retrieved. In these cases, * the headless analyzer should be gotten with {@link HeadlessAnalyzer#getInstance()}. * - * @param logFile The desired application log file. If null, the default application log file + * @param logFile The desired application log file. If null, the default application log file * will be used (see {@link Application#initializeLogging}). * @param scriptLogFile The desired scripting log file. If null, the default scripting log file * will be used (see {@link Application#initializeLogging}). - * @param useLog4j true if log4j is to be used; otherwise, false. If this class is being used by + * @param useLog4j true if log4j is to be used; otherwise, false. If this class is being used by * another tool as a library, using log4j might interfere with that tool. * @return An instance of a new headless analyzer. * @throws IllegalStateException if an application or headless analyzer instance has already been initialized. @@ -103,7 +103,7 @@ public class HeadlessAnalyzer { boolean useLog4j) throws IllegalStateException, IOException { // Prevent more than one headless analyzer from being instantiated. Too much about it - // messes with global system settings, so under the current design of Ghidra, allowing + // messes with global system settings, so under the current design of Ghidra, allowing // more than one to exist could result in unpredictable behavior. if (instance != null) { throw new IllegalStateException( @@ -141,7 +141,7 @@ public class HeadlessAnalyzer { /** * Gets a headless analyzer instance, with the assumption that the application has already been - * initialized. If this is called before the application has been initialized, it will + * initialized. If this is called before the application has been initialized, it will * initialize the application with no logging. * * @return An instance of a new headless analyzer. @@ -151,7 +151,7 @@ public class HeadlessAnalyzer { public static HeadlessAnalyzer getInstance() throws IOException { // Prevent more than one headless analyzer from being instantiated. Too much about it - // messes with global system settings, so under the current design of Ghidra, allowing + // messes with global system settings, so under the current design of Ghidra, allowing // more than one to exist could result in unpredictable behavior. if (instance != null) { return instance; @@ -185,8 +185,10 @@ public class HeadlessAnalyzer { layout = new GhidraApplicationLayout(); } catch (IOException e) { + Msg.debug(HeadlessAnalyzer.class, + "Unable to load the standard Ghidra application layout. " + e.getMessage() + + ". Attempting to load the Ghidra Jar application layout."); layout = new GhidraJarApplicationLayout(); - } return layout; } @@ -201,7 +203,7 @@ public class HeadlessAnalyzer { // Ghidra URL handler registration. There's no harm in doing this more than once. Handler.registerHandler(); - // Ensure that we are running in "headless mode", preventing Swing-based methods from + // Ensure that we are running in "headless mode", preventing Swing-based methods from // running (causing headless operation to lose focus). System.setProperty("java.awt.headless", "true"); System.setProperty(SystemUtilities.HEADLESS_PROPERTY, Boolean.TRUE.toString()); @@ -244,12 +246,12 @@ public class HeadlessAnalyzer { *

  • perform auto-analysis if not disabled
  • *
  • execute ordered list of post-scripts
  • * - * If no import files or directories have been specified the ordered list + * If no import files or directories have been specified the ordered list * of pre/post scripts will be executed once. * * @param ghidraURL ghidra URL for existing server repository and optional * folder path - * @param filesToImport directories and files to be imported (null or empty + * @param filesToImport directories and files to be imported (null or empty * is acceptable if we are in -process mode) * @throws IOException if there was an IO-related problem * @throws MalformedURLException specified URL is invalid @@ -370,16 +372,16 @@ public class HeadlessAnalyzer { *
  • perform auto-analysis if not disabled
  • *
  • execute ordered list of post-scripts
  • * - * If no import files or directories have been specified the ordered list + * If no import files or directories have been specified the ordered list * of pre/post scripts will be executed once. * - * @param projectLocation directory path of project + * @param projectLocation directory path of project * If project exists it will be opened, otherwise it will be created. * @param projectName project name * @param rootFolderPath root folder for imports * @param filesToImport directories and files to be imported (null or empty is acceptable if * we are in -process mode) - * @throws IOException if there was an IO-related problem. If caused by a failure to obtain a + * @throws IOException if there was an IO-related problem. If caused by a failure to obtain a * write-lock on the project the exception cause will a {@code LockException}. */ public void processLocal(String projectLocation, String projectName, String rootFolderPath, @@ -475,7 +477,7 @@ public class HeadlessAnalyzer { /** * Checks to see if the most recent analysis timed out. * - * @return true if the most recent analysis timed out; otherwise, false. + * @return true if the most recent analysis timed out; otherwise, false. */ public boolean checkAnalysisTimedOut() { return analysisTimedOut; @@ -766,7 +768,7 @@ public class HeadlessAnalyzer { Class c = Class.forName(className, true, classLoaderForDotClassScripts); if (GhidraScript.class.isAssignableFrom(c)) { - // No issues, but return null, which signifies we don't actually have a + // No issues, but return null, which signifies we don't actually have a // ResourceFile to associate with the script name return null; } @@ -962,9 +964,9 @@ public class HeadlessAnalyzer { * @param fileAbsolutePath Path of the file to analyze. * @param program The program to analyze. * @return true if the program file should be kept. If analysis or scripts have marked - * the program as temporary changes should not be saved. Returns false in + * the program as temporary changes should not be saved. Returns false in * these cases: - * - One of the scripts sets the Headless Continuation Option to "ABORT_AND_DELETE" or + * - One of the scripts sets the Headless Continuation Option to "ABORT_AND_DELETE" or * "CONTINUE_THEN_DELETE". */ private boolean analyzeProgram(String fileAbsolutePath, Program program) { @@ -1154,7 +1156,7 @@ public class HeadlessAnalyzer { Msg.info(this, "REPORT: Processing project file: " + domFile.getPathname()); - // This method already takes into account whether the user has set the "noanalysis" + // This method already takes into account whether the user has set the "noanalysis" // flag or not keepFile = analyzeProgram(domFile.getPathname(), program) || readOnlyFile; @@ -1237,7 +1239,7 @@ public class HeadlessAnalyzer { if (!readOnlyFile) { // can't change anything if read-only file - // Undo checkout of it is still checked-out and either the file is to be + // Undo checkout of it is still checked-out and either the file is to be // deleted, or we just checked it out and file changes have been committed if (domFile.isCheckedOut()) { if (!keepFile || @@ -1521,14 +1523,14 @@ public class HeadlessAnalyzer { try { // Perform the load. Note that loading 1 file may result in more than 1 thing getting - // loaded. + // loaded. loadResults = loadPrograms(fsrl, folderPath); Msg.info(this, "IMPORTING: Loaded " + (loadResults.size() - 1) + " additional files"); primary = loadResults.getPrimary(); Program primaryProgram = primary.getDomainObject(); - // Make sure we are allowed to save ALL programs to the project. If not, save none and + // Make sure we are allowed to save ALL programs to the project. If not, save none and // fail. if (!options.readOnly) { for (Loaded loaded : loadResults) { @@ -1549,7 +1551,7 @@ public class HeadlessAnalyzer { // TODO: Analyze non-primary programs (GP-2965). boolean doSave = analyzeProgram(fsrl.toString(), primaryProgram) && !options.readOnly; - // The act of marking the program as temporary by a script will signal + // The act of marking the program as temporary by a script will signal // us to discard any changes if (!doSave) { loadResults.forEach(e -> e.getDomainObject().setTemporary(true)); @@ -1776,7 +1778,7 @@ public class HeadlessAnalyzer { return; default: - // Just continue + // Just continue } runScriptsList(options.postScripts, options.postScriptFileMap, scriptState, @@ -1834,7 +1836,7 @@ public class HeadlessAnalyzer { } /** - * Ghidra project class required to gain access to specialized project constructor + * Ghidra project class required to gain access to specialized project constructor * for URL connection. */ private static class HeadlessProject extends DefaultProject { diff --git a/Ghidra/Framework/Utility/src/main/java/ghidra/GhidraJarApplicationLayout.java b/Ghidra/Framework/Utility/src/main/java/ghidra/GhidraJarApplicationLayout.java index 21b6922301..3b13e0f43a 100644 --- a/Ghidra/Framework/Utility/src/main/java/ghidra/GhidraJarApplicationLayout.java +++ b/Ghidra/Framework/Utility/src/main/java/ghidra/GhidraJarApplicationLayout.java @@ -23,11 +23,11 @@ import java.util.*; import generic.jar.ResourceFile; import ghidra.framework.ApplicationProperties; import ghidra.framework.GModule; -import utility.application.ApplicationLayout; +import ghidra.util.Msg; import utility.module.ModuleUtilities; /** - * The Ghidra jar application layout defines the customizable elements of the Ghidra application's + * The Ghidra jar application layout defines the customizable elements of the Ghidra application's * directory structure when running in "single jar mode." */ public class GhidraJarApplicationLayout extends GhidraApplicationLayout { @@ -51,7 +51,11 @@ public class GhidraJarApplicationLayout extends GhidraApplicationLayout { protected Collection findGhidraApplicationRootDirs() { List dirs = new ArrayList<>(); String appPropPath = "/_Root/Ghidra/" + ApplicationProperties.PROPERTY_FILE; - URL appPropUrl = ApplicationLayout.class.getResource(appPropPath); + URL appPropUrl = getClass().getResource(appPropPath); + if (appPropUrl == null) { + throw new IllegalStateException( + "The Ghidra Jar must have an application.properties file at " + appPropPath); + } ResourceFile rootDir = fromUrl(appPropUrl).getParentFile(); dirs.add(rootDir); return dirs; @@ -79,7 +83,12 @@ public class GhidraJarApplicationLayout extends GhidraApplicationLayout { @Override protected List findExtensionInstallationDirectories() { - URL extensionInstallUrl = ApplicationLayout.class.getResource("/_Root/Ghidra/Extensions"); + String path = "/_Root/Ghidra/Extensions"; + URL extensionInstallUrl = getClass().getResource(path); + if (extensionInstallUrl == null) { + Msg.debug(this, "No Extensions dir found at " + path); + return List.of(); + } ResourceFile extensionInstallDir = fromUrl(extensionInstallUrl); return Collections.singletonList(extensionInstallDir); } @@ -94,7 +103,7 @@ public class GhidraJarApplicationLayout extends GhidraApplicationLayout { String urlString = url.toExternalForm(); try { // Decode the URL to replace things like %20 with real spaces. - // Note: can't use URLDecoder.decode(String, Charset) because Utility must be + // Note: can't use URLDecoder.decode(String, Charset) because Utility must be // Java 1.8 compatible. urlString = URLDecoder.decode(urlString, "UTF-8"); }