mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-05 02:39:44 +02:00
GP-4294 - Fixed exception looking for extensions when running headlessly in fat jar mode
This commit is contained in:
parent
dffb5fd859
commit
359faba77a
3 changed files with 49 additions and 35 deletions
|
@ -62,9 +62,12 @@ public class BuildGhidraJarScript extends GhidraScript {
|
||||||
builder.addExcludedFileExtension(".pdf");
|
builder.addExcludedFileExtension(".pdf");
|
||||||
|
|
||||||
File installDir = Application.getInstallationDirectory().getFile(true);
|
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.
|
// uncomment the following line to create a src zip for debugging.
|
||||||
// builder.buildSrcZip(new File(installDir, "GhidraSrc.zip"), monitor);
|
// builder.buildSrcZip(new File(installDir, "GhidraSrc.zip"), monitor);
|
||||||
|
|
||||||
|
println("Finsished writing jar: " + file);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,16 +59,16 @@ import ghidra.util.task.TaskMonitor;
|
||||||
import utilities.util.FileUtilities;
|
import utilities.util.FileUtilities;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The class used kick-off and interact with headless processing. All headless options have been
|
* 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
|
* broken out into their own class: {@link HeadlessOptions}. This class is intended to be used
|
||||||
* one of two ways:
|
* one of two ways:
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>Used by {@link AnalyzeHeadless} to perform headless analysis based on arguments specified
|
* <li>Used by {@link AnalyzeHeadless} to perform headless analysis based on arguments specified
|
||||||
* on the command line.</li>
|
* on the command line.</li>
|
||||||
* <li>Used by another tool as a library to perform headless analysis.</li>
|
* <li>Used by another tool as a library to perform headless analysis.</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
* <p>
|
* <p>
|
||||||
* Note: This class is not thread safe.
|
* Note: This class is not thread safe.
|
||||||
*/
|
*/
|
||||||
public class HeadlessAnalyzer {
|
public class HeadlessAnalyzer {
|
||||||
|
|
||||||
|
@ -84,16 +84,16 @@ public class HeadlessAnalyzer {
|
||||||
private FileSystemService fsService;
|
private FileSystemService fsService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a headless analyzer, initializing the application if necessary with the specified
|
* Gets a headless analyzer, initializing the application if necessary with the specified
|
||||||
* logging parameters. An {@link IllegalStateException} will be thrown if the application has
|
* 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,
|
* already been initialized or a headless analyzer has already been retrieved. In these cases,
|
||||||
* the headless analyzer should be gotten with {@link HeadlessAnalyzer#getInstance()}.
|
* 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}).
|
* will be used (see {@link Application#initializeLogging}).
|
||||||
* @param scriptLogFile The desired scripting log file. If null, the default scripting log file
|
* @param scriptLogFile The desired scripting log file. If null, the default scripting log file
|
||||||
* will be used (see {@link Application#initializeLogging}).
|
* 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.
|
* another tool as a library, using log4j might interfere with that tool.
|
||||||
* @return An instance of a new headless analyzer.
|
* @return An instance of a new headless analyzer.
|
||||||
* @throws IllegalStateException if an application or headless analyzer instance has already been initialized.
|
* @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 {
|
boolean useLog4j) throws IllegalStateException, IOException {
|
||||||
|
|
||||||
// Prevent more than one headless analyzer from being instantiated. Too much about it
|
// 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.
|
// more than one to exist could result in unpredictable behavior.
|
||||||
if (instance != null) {
|
if (instance != null) {
|
||||||
throw new IllegalStateException(
|
throw new IllegalStateException(
|
||||||
|
@ -141,7 +141,7 @@ public class HeadlessAnalyzer {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a headless analyzer instance, with the assumption that the application has already been
|
* 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.
|
* initialize the application with no logging.
|
||||||
*
|
*
|
||||||
* @return An instance of a new headless analyzer.
|
* @return An instance of a new headless analyzer.
|
||||||
|
@ -151,7 +151,7 @@ public class HeadlessAnalyzer {
|
||||||
public static HeadlessAnalyzer getInstance() throws IOException {
|
public static HeadlessAnalyzer getInstance() throws IOException {
|
||||||
|
|
||||||
// Prevent more than one headless analyzer from being instantiated. Too much about it
|
// 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.
|
// more than one to exist could result in unpredictable behavior.
|
||||||
if (instance != null) {
|
if (instance != null) {
|
||||||
return instance;
|
return instance;
|
||||||
|
@ -185,8 +185,10 @@ public class HeadlessAnalyzer {
|
||||||
layout = new GhidraApplicationLayout();
|
layout = new GhidraApplicationLayout();
|
||||||
}
|
}
|
||||||
catch (IOException e) {
|
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();
|
layout = new GhidraJarApplicationLayout();
|
||||||
|
|
||||||
}
|
}
|
||||||
return layout;
|
return layout;
|
||||||
}
|
}
|
||||||
|
@ -201,7 +203,7 @@ public class HeadlessAnalyzer {
|
||||||
// Ghidra URL handler registration. There's no harm in doing this more than once.
|
// Ghidra URL handler registration. There's no harm in doing this more than once.
|
||||||
Handler.registerHandler();
|
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).
|
// running (causing headless operation to lose focus).
|
||||||
System.setProperty("java.awt.headless", "true");
|
System.setProperty("java.awt.headless", "true");
|
||||||
System.setProperty(SystemUtilities.HEADLESS_PROPERTY, Boolean.TRUE.toString());
|
System.setProperty(SystemUtilities.HEADLESS_PROPERTY, Boolean.TRUE.toString());
|
||||||
|
@ -244,12 +246,12 @@ public class HeadlessAnalyzer {
|
||||||
* <li>perform auto-analysis if not disabled</li>
|
* <li>perform auto-analysis if not disabled</li>
|
||||||
* <li>execute ordered list of post-scripts</li>
|
* <li>execute ordered list of post-scripts</li>
|
||||||
* </ol>
|
* </ol>
|
||||||
* 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.
|
* of pre/post scripts will be executed once.
|
||||||
*
|
*
|
||||||
* @param ghidraURL ghidra URL for existing server repository and optional
|
* @param ghidraURL ghidra URL for existing server repository and optional
|
||||||
* folder path
|
* 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)
|
* is acceptable if we are in -process mode)
|
||||||
* @throws IOException if there was an IO-related problem
|
* @throws IOException if there was an IO-related problem
|
||||||
* @throws MalformedURLException specified URL is invalid
|
* @throws MalformedURLException specified URL is invalid
|
||||||
|
@ -370,16 +372,16 @@ public class HeadlessAnalyzer {
|
||||||
* <li>perform auto-analysis if not disabled</li>
|
* <li>perform auto-analysis if not disabled</li>
|
||||||
* <li>execute ordered list of post-scripts</li>
|
* <li>execute ordered list of post-scripts</li>
|
||||||
* </ol>
|
* </ol>
|
||||||
* 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.
|
* 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.
|
* If project exists it will be opened, otherwise it will be created.
|
||||||
* @param projectName project name
|
* @param projectName project name
|
||||||
* @param rootFolderPath root folder for imports
|
* @param rootFolderPath root folder for imports
|
||||||
* @param filesToImport directories and files to be imported (null or empty is acceptable if
|
* @param filesToImport directories and files to be imported (null or empty is acceptable if
|
||||||
* we are in -process mode)
|
* 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}.
|
* write-lock on the project the exception cause will a {@code LockException}.
|
||||||
*/
|
*/
|
||||||
public void processLocal(String projectLocation, String projectName, String rootFolderPath,
|
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.
|
* 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() {
|
public boolean checkAnalysisTimedOut() {
|
||||||
return analysisTimedOut;
|
return analysisTimedOut;
|
||||||
|
@ -766,7 +768,7 @@ public class HeadlessAnalyzer {
|
||||||
Class<?> c = Class.forName(className, true, classLoaderForDotClassScripts);
|
Class<?> c = Class.forName(className, true, classLoaderForDotClassScripts);
|
||||||
|
|
||||||
if (GhidraScript.class.isAssignableFrom(c)) {
|
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
|
// ResourceFile to associate with the script name
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -962,9 +964,9 @@ public class HeadlessAnalyzer {
|
||||||
* @param fileAbsolutePath Path of the file to analyze.
|
* @param fileAbsolutePath Path of the file to analyze.
|
||||||
* @param program The program to analyze.
|
* @param program The program to analyze.
|
||||||
* @return true if the program file should be kept. If analysis or scripts have marked
|
* @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:
|
* 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".
|
* "CONTINUE_THEN_DELETE".
|
||||||
*/
|
*/
|
||||||
private boolean analyzeProgram(String fileAbsolutePath, Program program) {
|
private boolean analyzeProgram(String fileAbsolutePath, Program program) {
|
||||||
|
@ -1154,7 +1156,7 @@ public class HeadlessAnalyzer {
|
||||||
|
|
||||||
Msg.info(this, "REPORT: Processing project file: " + domFile.getPathname());
|
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
|
// flag or not
|
||||||
keepFile = analyzeProgram(domFile.getPathname(), program) || readOnlyFile;
|
keepFile = analyzeProgram(domFile.getPathname(), program) || readOnlyFile;
|
||||||
|
|
||||||
|
@ -1237,7 +1239,7 @@ public class HeadlessAnalyzer {
|
||||||
|
|
||||||
if (!readOnlyFile) { // can't change anything if read-only file
|
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
|
// deleted, or we just checked it out and file changes have been committed
|
||||||
if (domFile.isCheckedOut()) {
|
if (domFile.isCheckedOut()) {
|
||||||
if (!keepFile ||
|
if (!keepFile ||
|
||||||
|
@ -1521,14 +1523,14 @@ public class HeadlessAnalyzer {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
// Perform the load. Note that loading 1 file may result in more than 1 thing getting
|
// Perform the load. Note that loading 1 file may result in more than 1 thing getting
|
||||||
// loaded.
|
// loaded.
|
||||||
loadResults = loadPrograms(fsrl, folderPath);
|
loadResults = loadPrograms(fsrl, folderPath);
|
||||||
Msg.info(this, "IMPORTING: Loaded " + (loadResults.size() - 1) + " additional files");
|
Msg.info(this, "IMPORTING: Loaded " + (loadResults.size() - 1) + " additional files");
|
||||||
|
|
||||||
primary = loadResults.getPrimary();
|
primary = loadResults.getPrimary();
|
||||||
Program primaryProgram = primary.getDomainObject();
|
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.
|
// fail.
|
||||||
if (!options.readOnly) {
|
if (!options.readOnly) {
|
||||||
for (Loaded<Program> loaded : loadResults) {
|
for (Loaded<Program> loaded : loadResults) {
|
||||||
|
@ -1549,7 +1551,7 @@ public class HeadlessAnalyzer {
|
||||||
// TODO: Analyze non-primary programs (GP-2965).
|
// TODO: Analyze non-primary programs (GP-2965).
|
||||||
boolean doSave = analyzeProgram(fsrl.toString(), primaryProgram) && !options.readOnly;
|
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
|
// us to discard any changes
|
||||||
if (!doSave) {
|
if (!doSave) {
|
||||||
loadResults.forEach(e -> e.getDomainObject().setTemporary(true));
|
loadResults.forEach(e -> e.getDomainObject().setTemporary(true));
|
||||||
|
@ -1776,7 +1778,7 @@ public class HeadlessAnalyzer {
|
||||||
return;
|
return;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// Just continue
|
// Just continue
|
||||||
}
|
}
|
||||||
|
|
||||||
runScriptsList(options.postScripts, options.postScriptFileMap, scriptState,
|
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.
|
* for URL connection.
|
||||||
*/
|
*/
|
||||||
private static class HeadlessProject extends DefaultProject {
|
private static class HeadlessProject extends DefaultProject {
|
||||||
|
|
|
@ -23,11 +23,11 @@ import java.util.*;
|
||||||
import generic.jar.ResourceFile;
|
import generic.jar.ResourceFile;
|
||||||
import ghidra.framework.ApplicationProperties;
|
import ghidra.framework.ApplicationProperties;
|
||||||
import ghidra.framework.GModule;
|
import ghidra.framework.GModule;
|
||||||
import utility.application.ApplicationLayout;
|
import ghidra.util.Msg;
|
||||||
import utility.module.ModuleUtilities;
|
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."
|
* directory structure when running in "single jar mode."
|
||||||
*/
|
*/
|
||||||
public class GhidraJarApplicationLayout extends GhidraApplicationLayout {
|
public class GhidraJarApplicationLayout extends GhidraApplicationLayout {
|
||||||
|
@ -51,7 +51,11 @@ public class GhidraJarApplicationLayout extends GhidraApplicationLayout {
|
||||||
protected Collection<ResourceFile> findGhidraApplicationRootDirs() {
|
protected Collection<ResourceFile> findGhidraApplicationRootDirs() {
|
||||||
List<ResourceFile> dirs = new ArrayList<>();
|
List<ResourceFile> dirs = new ArrayList<>();
|
||||||
String appPropPath = "/_Root/Ghidra/" + ApplicationProperties.PROPERTY_FILE;
|
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();
|
ResourceFile rootDir = fromUrl(appPropUrl).getParentFile();
|
||||||
dirs.add(rootDir);
|
dirs.add(rootDir);
|
||||||
return dirs;
|
return dirs;
|
||||||
|
@ -79,7 +83,12 @@ public class GhidraJarApplicationLayout extends GhidraApplicationLayout {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected List<ResourceFile> findExtensionInstallationDirectories() {
|
protected List<ResourceFile> 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);
|
ResourceFile extensionInstallDir = fromUrl(extensionInstallUrl);
|
||||||
return Collections.singletonList(extensionInstallDir);
|
return Collections.singletonList(extensionInstallDir);
|
||||||
}
|
}
|
||||||
|
@ -94,7 +103,7 @@ public class GhidraJarApplicationLayout extends GhidraApplicationLayout {
|
||||||
String urlString = url.toExternalForm();
|
String urlString = url.toExternalForm();
|
||||||
try {
|
try {
|
||||||
// Decode the URL to replace things like %20 with real spaces.
|
// 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.
|
// Java 1.8 compatible.
|
||||||
urlString = URLDecoder.decode(urlString, "UTF-8");
|
urlString = URLDecoder.decode(urlString, "UTF-8");
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue