From fdda6b672eddb333750453d3b7d6175f4bb5e904 Mon Sep 17 00:00:00 2001
From: Ryan Kurtz
Date: Fri, 23 Sep 2022 05:15:36 -0400
Subject: [PATCH] GP-2604: More load library options
---
.../help/topics/ImporterPlugin/importer.htm | 46 ++--
.../ghidra/app/util/OptionsEditorPanel.java | 36 ++-
.../opinion/AbstractLibrarySupportLoader.java | 218 +++++++++++++-----
.../opinion/AbstractOrdinalSupportLoader.java | 20 +-
.../util/opinion/AbstractProgramLoader.java | 73 +++---
.../ghidra/app/util/opinion/BinaryLoader.java | 6 +-
.../ghidra/app/util/opinion/ElfLoader.java | 12 +-
.../app/util/opinion/IntelHexLoader.java | 6 +-
.../app/util/opinion/MotorolaHexLoader.java | 6 +-
.../ghidra/app/util/opinion/XmlLoader.java | 6 +-
.../ghidra/app/util/opinion/ApkLoader.java | 14 +-
.../Common/support/analyzeHeadlessREADME.html | 9 +
12 files changed, 294 insertions(+), 158 deletions(-)
diff --git a/Ghidra/Features/Base/src/main/help/help/topics/ImporterPlugin/importer.htm b/Ghidra/Features/Base/src/main/help/help/topics/ImporterPlugin/importer.htm
index 534d1bca12..85bebfc22d 100644
--- a/Ghidra/Features/Base/src/main/help/help/topics/ImporterPlugin/importer.htm
+++ b/Ghidra/Features/Base/src/main/help/help/topics/ImporterPlugin/importer.htm
@@ -29,38 +29,27 @@
+ Android APK
Common Object File Format (COFF)
-
+ Dalvik Executable (DEX)
Debug Symbols (DBG)
-
+ Dump File Loader
DYLD Shared Cache
-
Executable and Linking Format (ELF)
-
Ghidra Data Type Archive Format
-
GZF Input Format
-
Intel Hex
-
+ Java Class File
Mac OS X Mach-O
-
Module Definition (DEF)
-
Motorola Hex
-
New Executable (NE)
-
Old-style DOS Executable (MZ)
-
Portable Executable (PE)
-
Preferred Executable Format (PEF)
-
Program Mapfile (MAP)
-
Raw Binary
-
+ Relocatable Object Module Format (OMF)
XML Input Format
@@ -281,8 +270,22 @@
those symbols will remain at the address they were originally placed. If the option is
off, the symbols will move with the image base or the memory block.
+
+ Link Existing Project Libraries
- Load Local Libraries
+
+ Searches the project for existing library programs and creates external references to
+ them.
+
+
+ Project Library Search Folder
+
+
+ The project folder that will get searched for existing library programs. If left
+ empty, the folder that the main program is being imported to will be searched.
+
+
+ Load Local Libraries From Disk
Searches the executable's directory to recursively resolve the external libraries used
@@ -292,7 +295,7 @@
in these programs will be resolved.
- Load System Libraries
+ Load System Libraries From Disk
Searches a user-defined path list to recursively resolve the external libraries used
@@ -309,6 +312,13 @@
Specifies how many levels deep the depth-first library dependency tree will be
traversed when loading local or system libraries.
+
+ Library Destination Folder
+
+
+ The project folder where newly loaded library programs will get created. If left
+ empty, they will get created in the same folder as the main program being imported.
+
COFF Options
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/OptionsEditorPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/OptionsEditorPanel.java
index f375b942da..9c77b048be 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/OptionsEditorPanel.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/OptionsEditorPanel.java
@@ -28,12 +28,15 @@ import javax.swing.event.DocumentListener;
import org.apache.commons.collections4.map.LazyMap;
import docking.DockingWindowManager;
+import docking.options.editor.ButtonPanelFactory;
import docking.widgets.checkbox.GCheckBox;
import docking.widgets.combobox.GComboBox;
import docking.widgets.label.GLabel;
import docking.widgets.textfield.IntegerTextField;
import ghidra.app.util.opinion.AbstractLibrarySupportLoader;
import ghidra.app.util.opinion.LibraryPathsDialog;
+import ghidra.framework.main.DataTreeDialog;
+import ghidra.framework.model.DomainFolder;
import ghidra.program.model.address.*;
import ghidra.util.exception.AssertException;
import ghidra.util.layout.*;
@@ -43,7 +46,7 @@ import ghidra.util.layout.*;
* in a list of Options and generates editors for each of them on th fly.
*/
public class OptionsEditorPanel extends JPanel {
- private static final int MAX_PER_COLUMN = 10;
+ private static final int MAX_PER_COLUMN = 11;
private static final int MAX_BOOLEANS_WITH_SELECT_ALL = 5;
private int columns;
private AddressFactoryService addressFactoryService;
@@ -177,9 +180,13 @@ public class OptionsEditorPanel extends JPanel {
public Component getEditorComponent(Option option) {
- //special case for load library paths
+ // Special cases for library link/load options
+ if (option.getName().equals(AbstractLibrarySupportLoader.LINK_SEARCH_FOLDER_OPTION_NAME) ||
+ option.getName().equals(AbstractLibrarySupportLoader.LIBRARY_DEST_FOLDER_OPTION_NAME)) {
+ return buildProjectFolderEditor(option);
+ }
if (option.getName().equals(AbstractLibrarySupportLoader.SYSTEM_LIBRARY_OPTION_NAME)) {
- return buildLoadLibraryPathsEditor(option);
+ return buildPathsEditor(option);
}
Component customEditorComponent = option.getCustomEditorComponent();
@@ -216,7 +223,7 @@ public class OptionsEditorPanel extends JPanel {
}
}
- private Component buildLoadLibraryPathsEditor(Option option) {
+ private Component buildPathsEditor(Option option) {
JPanel panel = new JPanel(new BorderLayout());
JButton button = new JButton("Edit Paths");
button.addActionListener(
@@ -233,6 +240,27 @@ public class OptionsEditorPanel extends JPanel {
return panel;
}
+ private Component buildProjectFolderEditor(Option option) {
+ JPanel panel = new JPanel(new BorderLayout());
+ JTextField textField = new JTextField();
+ textField.setEditable(false);
+ JButton button = ButtonPanelFactory.createButton(ButtonPanelFactory.BROWSE_TYPE);
+ button.addActionListener(e -> {
+ DataTreeDialog dataTreeDialog = new DataTreeDialog(this,
+ "Choose a project folder", DataTreeDialog.CHOOSE_FOLDER);
+ dataTreeDialog.setSelectedFolder(null);
+ dataTreeDialog.showComponent();
+ DomainFolder folder = dataTreeDialog.getDomainFolder();
+ if (folder != null) {
+ textField.setText(folder.getPathname());
+ option.setValue(folder.getPathname());
+ }
+ });
+ panel.add(textField, BorderLayout.CENTER);
+ panel.add(button, BorderLayout.EAST);
+ return panel;
+ }
+
private Component getAddressSpaceEditorComponent(Option option) {
JComboBox combo = new GComboBox<>();
AddressFactory addressFactory = addressFactoryService.getAddressFactory();
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/AbstractLibrarySupportLoader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/AbstractLibrarySupportLoader.java
index aa48a41f2d..c07f075e62 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/AbstractLibrarySupportLoader.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/AbstractLibrarySupportLoader.java
@@ -50,15 +50,24 @@ import utilities.util.FileUtilities;
*/
public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader {
- public static final String LOCAL_LIBRARY_OPTION_NAME = "Load Local Libraries";
+ public static final String LINK_EXISTING_OPTION_NAME = "Link Existing Project Libraries";
+ static final boolean LINK_EXISTING_OPTION_DEFAULT = true;
+
+ public static final String LINK_SEARCH_FOLDER_OPTION_NAME = "Project Library Search Folder";
+ static final String LINK_SEARCH_FOLDER_OPTION_DEFAULT = "";
+
+ public static final String LOCAL_LIBRARY_OPTION_NAME = "Load Local Libraries From Disk";
static final boolean LOCAL_LIBRARY_OPTION_DEFAULT = false;
- public static final String SYSTEM_LIBRARY_OPTION_NAME = "Load System Libraries";
+ public static final String SYSTEM_LIBRARY_OPTION_NAME = "Load System Libraries From Disk";
static final boolean SYSTEM_LIBRARY_OPTION_DEFAULT = false;
public static final String DEPTH_OPTION_NAME = "Recursive Library Load Depth";
static final int DEPTH_OPTION_DEFAULT = 1;
+ public static final String LIBRARY_DEST_FOLDER_OPTION_NAME = "Library Destination Folder";
+ static final String LIBRARY_DEST_FOLDER_OPTION_DEFAULT = "";
+
/**
* Loads bytes in a particular format into the given {@link Program}.
*
@@ -76,11 +85,11 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
throws CancelledException, IOException;
@Override
- protected List loadProgram(ByteProvider provider, String programName,
+ protected List loadProgram(ByteProvider provider, String programName,
DomainFolder programFolder, LoadSpec loadSpec, List options, MessageLog log,
Object consumer, TaskMonitor monitor) throws CancelledException, IOException {
- List programList = new ArrayList<>();
+ List loadedProgramList = new ArrayList<>();
List libraryNameList = new ArrayList<>();
boolean success = false;
@@ -88,21 +97,19 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
// Load the primary program
Program program = doLoad(provider, programName, programFolder, loadSpec,
libraryNameList, options, consumer, log, monitor);
- programList.add(program);
+ loadedProgramList.add(new LoadedProgram(program, programFolder));
- // Load the libraries, if applicable
- if (shouldLoadLibraries(options)) {
- List libraries = loadLibraries(provider, program, programFolder, loadSpec,
- options, log, consumer, libraryNameList, monitor);
- programList.addAll(libraries);
- }
+ // Load the libraries
+ List libraries = loadLibraries(provider, program, programFolder,
+ loadSpec, options, log, consumer, libraryNameList, monitor);
+ loadedProgramList.addAll(libraries);
success = true;
- return programList;
+ return loadedProgramList;
}
finally {
if (!success) {
- release(programList, consumer);
+ release(loadedProgramList, consumer);
}
}
}
@@ -126,11 +133,14 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
}
@Override
- protected void postLoadProgramFixups(List loadedPrograms, DomainFolder folder,
- List options, MessageLog messageLog, TaskMonitor monitor)
- throws CancelledException, IOException {
- if (isLoadLocalLibraries(options) || isLoadSystemLibraries(options)) {
- fixupExternalLibraries(loadedPrograms, folder, true, messageLog, monitor);
+ protected void postLoadProgramFixups(List loadedPrograms, List options,
+ MessageLog messageLog, TaskMonitor monitor) throws CancelledException, IOException {
+ if (isLinkExistingLibraries(options) || isLoadLocalLibraries(options) ||
+ isLoadSystemLibraries(options)) {
+ DomainFolder programFolder = loadedPrograms.get(0).destinationFolder();
+ DomainFolder linkSearchFolder = getLinkSearchFolder(programFolder, options);
+ fixupExternalLibraries(loadedPrograms.stream().map(e -> e.program()).toList(),
+ linkSearchFolder, true, messageLog, monitor);
}
}
@@ -149,12 +159,18 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
DomainObject domainObject, boolean loadIntoProgram) {
List list =
super.getDefaultOptions(provider, loadSpec, domainObject, loadIntoProgram);
+ list.add(new Option(LINK_EXISTING_OPTION_NAME, LINK_EXISTING_OPTION_DEFAULT, Boolean.class,
+ Loader.COMMAND_LINE_ARG_PREFIX + "-linkExistingProjectLibraries"));
+ list.add(new Option(LINK_SEARCH_FOLDER_OPTION_NAME, LINK_SEARCH_FOLDER_OPTION_DEFAULT,
+ String.class, Loader.COMMAND_LINE_ARG_PREFIX + "-projectLibrarySearchFolder"));
list.add(new Option(LOCAL_LIBRARY_OPTION_NAME, LOCAL_LIBRARY_OPTION_DEFAULT, Boolean.class,
Loader.COMMAND_LINE_ARG_PREFIX + "-loadLocalLibraries"));
list.add(new Option(SYSTEM_LIBRARY_OPTION_NAME, SYSTEM_LIBRARY_OPTION_DEFAULT, Boolean.class,
Loader.COMMAND_LINE_ARG_PREFIX + "-loadSystemLibraries"));
list.add(new Option(DEPTH_OPTION_NAME, DEPTH_OPTION_DEFAULT, Integer.class,
Loader.COMMAND_LINE_ARG_PREFIX + "-libraryLoadDepth"));
+ list.add(new Option(LIBRARY_DEST_FOLDER_OPTION_NAME, LIBRARY_DEST_FOLDER_OPTION_DEFAULT,
+ String.class, Loader.COMMAND_LINE_ARG_PREFIX + "-libraryDestinationFolder"));
return list;
}
@@ -164,7 +180,9 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
if (options != null) {
for (Option option : options) {
String name = option.getName();
- if (name.equals(LOCAL_LIBRARY_OPTION_NAME) || name.equals(SYSTEM_LIBRARY_OPTION_NAME)) {
+ if (name.equals(LINK_EXISTING_OPTION_NAME) ||
+ name.equals(LOCAL_LIBRARY_OPTION_NAME) ||
+ name.equals(SYSTEM_LIBRARY_OPTION_NAME)) {
if (!Boolean.class.isAssignableFrom(option.getValueClass())) {
return "Invalid type for option: " + name + " - " + option.getValueClass();
}
@@ -174,11 +192,66 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
return "Invalid type for option: " + name + " - " + option.getValueClass();
}
}
+ else if (name.equals(LINK_SEARCH_FOLDER_OPTION_NAME) ||
+ name.equals(LIBRARY_DEST_FOLDER_OPTION_NAME)) {
+ if (!String.class.isAssignableFrom(option.getValueClass())) {
+ return "Invalid type for option: " + name + " - " + option.getValueClass();
+ }
+ }
}
}
return super.validateOptions(provider, loadSpec, options, program);
}
+ /**
+ * Checks to see if existing libraries should be linked
+ *
+ * @param options a {@link List} of {@link Option}s
+ * @return True if existing libraries should be linked; otherwise, false
+ */
+ protected boolean isLinkExistingLibraries(List options) {
+ boolean isLinkExistingLibraries = LINK_EXISTING_OPTION_DEFAULT;
+ if (options != null) {
+ for (Option option : options) {
+ String optName = option.getName();
+ if (optName.equals(LINK_EXISTING_OPTION_NAME)) {
+ isLinkExistingLibraries = (Boolean) option.getValue();
+ }
+ }
+ }
+ return isLinkExistingLibraries;
+ }
+
+ /**
+ * Gets the {@link DomainFolder project folder} to search for existing libraries
+ *
+ * @param programFolder The {@link DomainFolder} that the main program is being loaded into
+ * @param options a {@link List} of {@link Option}s
+ * @return The path of the project folder to search for existing libraries, or null if no
+ * project folders should be searched
+ */
+ protected DomainFolder getLinkSearchFolder(DomainFolder programFolder,
+ List options) {
+ if (!shouldSearchAllPaths(options) && !isLinkExistingLibraries(options)) {
+ return null;
+ }
+ String folderPath = LINK_SEARCH_FOLDER_OPTION_DEFAULT;
+ if (options != null) {
+ for (Option option : options) {
+ String optName = option.getName();
+ if (optName.equals(LINK_SEARCH_FOLDER_OPTION_NAME)) {
+ folderPath = (String) option.getValue();
+ }
+ }
+ }
+
+ if (folderPath.equals(LINK_SEARCH_FOLDER_OPTION_DEFAULT)) {
+ return programFolder;
+ }
+
+ return programFolder.getProjectData().getFolder(FilenameUtils.separatorsToUnix(folderPath));
+ }
+
/**
* Checks to see if local libraries should be loaded. Local libraries are libraries that live
* in the same directory as the imported program.
@@ -239,23 +312,39 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
}
/**
- * Checks whether or not libraries should be loaded (local or system)
+ * Gets the {@link DomainFolder project folder} to load the libraries into
*
+ * @param programFolder The {@link DomainFolder} that the main program is being loaded into
* @param options a {@link List} of {@link Option}s
- * @return True if any libraries should be loaded (local or system); otherwise, false
+ * @return The path of the project folder to load the libraries into
*/
- protected boolean shouldLoadLibraries(List options) {
- return (isLoadLocalLibraries(options) || isLoadSystemLibraries(options)) &&
- getLibraryLoadDepth(options) > 0;
+ protected DomainFolder getLibraryDestinationFolder(DomainFolder programFolder,
+ List options) {
+ String folderPath = LIBRARY_DEST_FOLDER_OPTION_DEFAULT;
+ if (options != null) {
+ for (Option option : options) {
+ String optName = option.getName();
+ if (optName.equals(LIBRARY_DEST_FOLDER_OPTION_NAME)) {
+ folderPath = (String) option.getValue();
+ }
+ }
+ }
+
+ if (folderPath.equals(LIBRARY_DEST_FOLDER_OPTION_DEFAULT)) {
+ return programFolder;
+ }
+
+ return programFolder.getProjectData().getFolder(FilenameUtils.separatorsToUnix(folderPath));
}
/**
* Checks whether or not to search for libraries using all possible search paths, regardless
* of what options are set
*
+ * @param options a {@link List} of {@link Option}s
* @return True if all possible search paths should be used, regardless of what options are set
*/
- protected boolean shouldSearchAllPaths() {
+ protected boolean shouldSearchAllPaths(List options) {
return false;
}
@@ -340,7 +429,7 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
protected boolean processLibrary(Program library, String libraryName, File libraryFile,
ByteProvider provider, LoadSpec loadSpec, List options, MessageLog log,
TaskMonitor monitor) throws IOException, CancelledException {
- return true;
+ return isLoadLocalLibraries(options) || isLoadSystemLibraries(options);
}
/**
@@ -361,16 +450,18 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
* @throws IOException if there was an IO-related problem loading
* @throws CancelledException if the user cancelled the load
*/
- private List loadLibraries(ByteProvider provider, Program program,
+ private List loadLibraries(ByteProvider provider, Program program,
DomainFolder programFolder, LoadSpec desiredLoadSpec, List options,
MessageLog log, Object consumer, List libraryNameList, TaskMonitor monitor)
throws CancelledException, IOException {
- List programList = new ArrayList<>();
+ List loadedPrograms = new ArrayList<>();
Set processed = new HashSet<>();
Queue unprocessed =
createUnprocessedQueue(libraryNameList, getLibraryLoadDepth(options));
List searchPaths = getLibrarySearchPaths(provider, options);
+ DomainFolder linkSearchFolder = getLinkSearchFolder(programFolder, options);
+ DomainFolder libraryDestFolder = getLibraryDestinationFolder(programFolder, options);
while (!unprocessed.isEmpty()) {
monitor.checkCanceled();
@@ -380,8 +471,12 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
if (depth == 0 || processed.contains(libraryName)) {
continue;
}
+ processed.add(libraryName);
boolean foundLibrary = false;
- if (findLibrary(libraryName, programFolder) == null) {
+ if (linkSearchFolder != null && findLibrary(libraryName, linkSearchFolder) != null) {
+ log.appendMsg("Library " + libraryName + ": Already loaded ");
+ }
+ else if (!searchPaths.isEmpty()) {
String simpleLibraryName = FilenameUtils.getName(libraryName);
List candidateLibraryFiles =
@@ -399,7 +494,7 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
foundLibrary = true;
if (processLibrary(library, libraryName, candidateLibraryFile, provider,
desiredLoadSpec, options, log, monitor)) {
- programList.add(library);
+ loadedPrograms.add(new LoadedProgram(library, libraryDestFolder));
log.appendMsg(
"Library " + libraryName + ": Saving " + candidateLibraryFile);
}
@@ -415,14 +510,8 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
log.appendMsg("Library " + libraryName + ": Not found");
}
}
- else {
- log.appendMsg("Library " + libraryName + ": Already loaded ");
- }
- processed.add(libraryName);
}
- log.appendMsg(
- "Finished importing referenced libraries for: " + program.getName());
- return programList;
+ return loadedPrograms;
}
/**
@@ -548,7 +637,7 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
* a {@link ByteProvider} available.
*
* @param libraryName The name of the library to load
- * @param programFolder The domain folder where the new program will be stored, if null
+ * @param libraryFolder The domain folder where the new library program will be stored, if null
* the program should not be pre-saved. NOTE: the newly imported libraries will not be written
* to this folder yet, that is handled in a later follow on step.
* @param libraryFile The library file to load
@@ -563,7 +652,7 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
* @throws CancelledException if the user cancelled the load operation
* @throws IOException if there was an IO-related error during the load
*/
- private Program loadLibrary(String libraryName, DomainFolder programFolder, File libraryFile,
+ private Program loadLibrary(String libraryName, DomainFolder libraryFolder, File libraryFile,
LoadSpec desiredLoadSpec, List libraryNameList, List options,
Object consumer, MessageLog log, TaskMonitor monitor)
throws CancelledException, IOException {
@@ -579,7 +668,7 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
return null;
}
- Program library = doLoad(provider, libraryName, programFolder, libLoadSpec,
+ Program library = doLoad(provider, libraryName, libraryFolder, libLoadSpec,
libraryNameList, options, consumer, log, monitor);
if (library == null) {
@@ -668,16 +757,16 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
*
* @param programs the list of programs to resolve against each other. Programs not saved
* to the project will be considered as a valid external library.
- * @param domainFolder the {@link DomainFolder} folder within which imported libraries will
- * be searched. This folder will be searched if a library is not found within the
- * list of programs supplied. If null, only the list of programs will be considered.
+ * @param searchFolder the {@link DomainFolder} which imported libraries will be searched.
+ * This folder will be searched if a library is not found within the list of
+ * programs supplied. If null, only the list of programs will be considered.
* @param saveIfModified flag to have this method save any programs it modifies
* @param messageLog log for messages.
* @param monitor the task monitor
* @throws IOException if there was an IO-related problem resolving.
* @throws CancelledException if the user cancelled the load.
*/
- private void fixupExternalLibraries(List programs, DomainFolder domainFolder,
+ private void fixupExternalLibraries(List programs, DomainFolder searchFolder,
boolean saveIfModified, MessageLog messageLog, TaskMonitor monitor)
throws CancelledException, IOException {
@@ -703,7 +792,7 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
monitor.setMessage("Resolving..." + program.getName());
int id = program.startTransaction("Resolving external references");
try {
- resolveExternalLibraries(program, progsByName, domainFolder, monitor, messageLog);
+ resolveExternalLibraries(program, progsByName, searchFolder, monitor, messageLog);
}
finally {
program.endTransaction(id, true);
@@ -725,15 +814,15 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
* @param progsByName map of recently imported programs to be considered
* first when resolving external Libraries. Programs not saved to the project
* will be ignored.
- * @param domainFolder the {@link DomainFolder} folder within which imported libraries will
- * be searched. This folder will be searched if a library is not found within the
- * progsByName map. If null, only progsByName will be considered.
+ * @param searchFolder the {@link DomainFolder} which imported libraries will be searched.
+ * This folder will be searched if a library is not found within the list of
+ * programs supplied. If null, only the list of programs will be considered.
* @param messageLog log for messages.
* @param monitor the task monitor
* @throws CancelledException if the user cancelled the load.
*/
private void resolveExternalLibraries(Program program, Map progsByName,
- DomainFolder domainFolder, TaskMonitor monitor, MessageLog messageLog)
+ DomainFolder searchFolder, TaskMonitor monitor, MessageLog messageLog)
throws CancelledException {
ExternalManager extManager = program.getExternalManager();
String[] extLibNames = extManager.getExternalLibraryNames();
@@ -745,8 +834,7 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
monitor.checkCanceled();
try {
String externalFileName = FilenameUtils.getName(externalLibName);
- DomainObject matchingExtProgram =
- findLibraryWithCaseCorrectSearch(progsByName, externalFileName);
+ DomainObject matchingExtProgram = findLibrary(progsByName, externalFileName);
if (matchingExtProgram != null && matchingExtProgram.getDomainFile().exists()) {
extManager.setExternalPath(externalLibName,
matchingExtProgram.getDomainFile().getPathname(), false);
@@ -754,8 +842,7 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
matchingExtProgram.getDomainFile().getPathname() + "]");
}
else {
- DomainFile alreadyImportedLib =
- findLibrary(externalLibName, domainFolder);
+ DomainFile alreadyImportedLib = findLibrary(externalLibName, searchFolder);
if (alreadyImportedLib != null) {
extManager.setExternalPath(externalLibName,
alreadyImportedLib.getPathname(), false);
@@ -806,29 +893,32 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
private List getLibrarySearchPaths(ByteProvider provider, List options) {
String parent = getProviderFilePath(provider);
List paths = new ArrayList<>();
- if (shouldSearchAllPaths() || isLoadLocalLibraries(options) && parent != null) {
+ if (shouldSearchAllPaths(options) || isLoadLocalLibraries(options) && parent != null) {
paths.add(parent);
}
- if (shouldSearchAllPaths() || isLoadSystemLibraries(options)) {
+ if (shouldSearchAllPaths(options) || isLoadSystemLibraries(options)) {
paths.addAll(LibrarySearchPathManager.getLibraryPathsList());
}
return paths;
}
-
+
/**
- * Looks up a library in the given {@link Program} map using the appropriate case comparisons
+ * Find the library within the given {@link Map} of {@link Program}s
*
* @param programsByName The map to search
* @param libraryName The library name to lookup
- * @return A {@link Program} that matches the given library name using appropriate case
- * comparisons, or null if one was not found
+ * @return The found {@link Program} or null if not found
*/
- private Program findLibraryWithCaseCorrectSearch(Map programsByName,
- String libraryName) {
+ private Program findLibrary(Map programsByName, String libraryName) {
Comparator comparator = getLibraryNameComparator();
- for (String s : programsByName.keySet()) {
- if (comparator.compare(libraryName, s) == 0) {
- return programsByName.get(s);
+ boolean noExtension = FilenameUtils.getExtension(libraryName).equals("");
+ for (String key : programsByName.keySet()) {
+ String candidateName = key;
+ if (isOptionalLibraryFilenameExtensions() && noExtension) {
+ candidateName = FilenameUtils.getBaseName(candidateName);
+ }
+ if (comparator.compare(candidateName, libraryName) == 0) {
+ return programsByName.get(key);
}
}
return null;
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/AbstractOrdinalSupportLoader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/AbstractOrdinalSupportLoader.java
index c2b9a11241..c835e89d17 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/AbstractOrdinalSupportLoader.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/AbstractOrdinalSupportLoader.java
@@ -23,7 +23,6 @@ import java.util.List;
import ghidra.app.util.Option;
import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.importer.MessageLog;
-import ghidra.framework.model.DomainFolder;
import ghidra.framework.model.DomainObject;
import ghidra.framework.options.Options;
import ghidra.program.model.listing.Function;
@@ -69,13 +68,8 @@ public abstract class AbstractOrdinalSupportLoader extends AbstractLibrarySuppor
}
@Override
- protected boolean shouldLoadLibraries(List options) {
- return shouldPerformOrdinalLookup(options) || super.shouldLoadLibraries(options);
- }
-
- @Override
- protected boolean shouldSearchAllPaths() {
- return true;
+ protected boolean shouldSearchAllPaths(List options) {
+ return shouldPerformOrdinalLookup(options);
}
@Override
@@ -122,15 +116,15 @@ public abstract class AbstractOrdinalSupportLoader extends AbstractLibrarySuppor
}
@Override
- protected void postLoadProgramFixups(List loadedPrograms, DomainFolder folder,
- List options, MessageLog messageLog, TaskMonitor monitor)
- throws CancelledException, IOException {
+ protected void postLoadProgramFixups(List loadedPrograms, List options,
+ MessageLog messageLog, TaskMonitor monitor) throws CancelledException, IOException {
monitor.initialize(loadedPrograms.size());
if (shouldPerformOrdinalLookup(options)) {
- for (Program p : loadedPrograms) {
+ for (LoadedProgram loadedProgram : loadedPrograms) {
monitor.checkCanceled();
+ Program p = loadedProgram.program();
int id = p.startTransaction("Ordinal fixups");
boolean success = false;
try {
@@ -148,7 +142,7 @@ public abstract class AbstractOrdinalSupportLoader extends AbstractLibrarySuppor
}
LibraryLookupTable.cleanup();
- super.postLoadProgramFixups(loadedPrograms, folder, options, messageLog, monitor);
+ super.postLoadProgramFixups(loadedPrograms, options, messageLog, monitor);
}
/**
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/AbstractProgramLoader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/AbstractProgramLoader.java
index 91586ac240..25ff40b48b 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/AbstractProgramLoader.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/AbstractProgramLoader.java
@@ -15,11 +15,10 @@
*/
package ghidra.app.util.opinion;
-import java.util.ArrayList;
-import java.util.List;
-
import java.io.IOException;
import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
import ghidra.app.plugin.processors.generic.MemoryBlockDefinition;
import ghidra.app.util.Option;
@@ -57,6 +56,14 @@ public abstract class AbstractProgramLoader implements Loader {
public static final String APPLY_LABELS_OPTION_NAME = "Apply Processor Defined Labels";
public static final String ANCHOR_LABELS_OPTION_NAME = "Anchor Processor Defined Labels";
+ /**
+ * A {@link Program} with its associated {@link DomainFolder destination folder}
+ *
+ * @param program The {@link Program}
+ * @param destinationFolder The {@link DomainFolder} where the program will get loaded to
+ */
+ public record LoadedProgram(Program program, DomainFolder destinationFolder) {/**/}
+
/**
* Loads program bytes in a particular format as a new {@link Program}. Multiple
* {@link Program}s may end up getting created, depending on the nature of the format.
@@ -70,12 +77,12 @@ public abstract class AbstractProgramLoader implements Loader {
* @param log The message log.
* @param consumer A consumer object for {@link Program}s generated.
* @param monitor A cancelable task monitor.
- * @return A list of loaded {@link Program}s (element 0 corresponds to primary loaded
- * {@link Program}).
+ * @return A list of {@link LoadedProgram loaded programs} (element 0 corresponds to primary
+ * loaded {@link Program}).
* @throws IOException if there was an IO-related problem loading.
* @throws CancelledException if the user cancelled the load.
*/
- protected abstract List loadProgram(ByteProvider provider, String programName,
+ protected abstract List loadProgram(ByteProvider provider, String programName,
DomainFolder programFolder, LoadSpec loadSpec, List options, MessageLog log,
Object consumer, TaskMonitor monitor) throws IOException, CancelledException;
@@ -115,54 +122,56 @@ public abstract class AbstractProgramLoader implements Loader {
return results;
}
- List programs =
+ List loadedPrograms =
loadProgram(provider, name, folder, loadSpec, options, messageLog, consumer, monitor);
boolean success = false;
try {
monitor.checkCanceled();
- List programsToFixup = new ArrayList<>();
- for (Program loadedProgram : programs) {
+ List programsToFixup = new ArrayList<>();
+ for (LoadedProgram loadedProgram : loadedPrograms) {
monitor.checkCanceled();
- applyProcessorLabels(options, loadedProgram);
+ Program program = loadedProgram.program();
- loadedProgram.setEventsEnabled(true);
+ applyProcessorLabels(options, program);
+
+ program.setEventsEnabled(true);
// TODO: null should not be used as a determinant for saving; don't allow null
// folders?
- if (folder == null) {
- results.add(loadedProgram);
+ if (loadedProgram.destinationFolder() == null) {
+ results.add(program);
continue;
}
- String domainFileName = loadedProgram.getName();
+ String domainFileName = program.getName();
if (isOverrideMainProgramName()) {
// If this is the main imported program, use the given name, otherwise, use the
// internal program name. The first program in the list is the main imported program
- if (loadedProgram == programs.get(0)) {
+ if (program == loadedPrograms.get(0).program()) {
domainFileName = name;
}
}
- if (createProgramFile(loadedProgram, folder, domainFileName, messageLog,
- monitor)) {
- results.add(loadedProgram);
+ if (createProgramFile(program, loadedProgram.destinationFolder(), domainFileName,
+ messageLog, monitor)) {
+ results.add(program);
programsToFixup.add(loadedProgram);
}
else {
- loadedProgram.release(consumer); // some kind of exception happened; see MessageLog
+ program.release(consumer); // some kind of exception happened; see MessageLog
}
}
// Subclasses can perform custom post-load fix-ups
- postLoadProgramFixups(programsToFixup, folder, options, messageLog, monitor);
+ postLoadProgramFixups(programsToFixup, options, messageLog, monitor);
success = true;
}
finally {
if (!success) {
- release(programs, consumer);
+ release(loadedPrograms, consumer);
}
}
@@ -231,20 +240,18 @@ public abstract class AbstractProgramLoader implements Loader {
}
/**
- * This gets called after the given list of {@link Program}s is finished loading. It provides
- * subclasses an opportunity to do follow-on actions to the load.
+ * This gets called after the given list of {@link LoadedProgram programs}s is finished loading.
+ * It provides subclasses an opportunity to do follow-on actions to the load.
*
- * @param loadedPrograms The {@link Program}s that got loaded.
- * @param folder The folder the programs were loaded to.
+ * @param loadedPrograms The {@link LoadedProgram programs} that got loaded.
* @param options The load options.
* @param messageLog The message log.
* @param monitor A cancelable task monitor.
* @throws IOException if there was an IO-related problem loading.
* @throws CancelledException if the user cancelled the load.
*/
- protected void postLoadProgramFixups(List loadedPrograms, DomainFolder folder,
- List options, MessageLog messageLog, TaskMonitor monitor)
- throws CancelledException, IOException {
+ protected void postLoadProgramFixups(List loadedPrograms, List options,
+ MessageLog messageLog, TaskMonitor monitor) throws CancelledException, IOException {
// Default behavior is to do nothing.
}
@@ -449,14 +456,14 @@ public abstract class AbstractProgramLoader implements Loader {
}
/**
- * Releases the given consumer from each of the provided {@link DomainObject}s.
+ * Releases the given consumer from each of the provided {@link LoadedProgram}s.
*
- * @param domainObjects A list of {@link DomainObject}s which are no longer being used.
+ * @param loadedPrograms A list of {@link LoadedProgram}s which are no longer being used.
* @param consumer The consumer that was marking the {@link DomainObject}s as being used.
*/
- protected final void release(List extends DomainObject> domainObjects, Object consumer) {
- for (DomainObject dobj : domainObjects) {
- dobj.release(consumer);
+ protected final void release(List loadedPrograms, Object consumer) {
+ for (LoadedProgram loadedProgram : loadedPrograms) {
+ loadedProgram.program().release(consumer);
}
}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/BinaryLoader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/BinaryLoader.java
index 8a109cb156..766ab3f58c 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/BinaryLoader.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/BinaryLoader.java
@@ -269,7 +269,7 @@ public class BinaryLoader extends AbstractProgramLoader {
}
@Override
- protected List loadProgram(ByteProvider provider, String programName,
+ protected List loadProgram(ByteProvider provider, String programName,
DomainFolder programFolder, LoadSpec loadSpec, List options, MessageLog log,
Object consumer, TaskMonitor monitor) throws IOException, CancelledException {
LanguageCompilerSpecPair pair = loadSpec.getLanguageCompilerSpec();
@@ -294,9 +294,9 @@ public class BinaryLoader extends AbstractProgramLoader {
prog = null;
}
}
- List results = new ArrayList();
+ List results = new ArrayList<>();
if (prog != null) {
- results.add(prog);
+ results.add(new LoadedProgram(prog, programFolder));
}
return results;
}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/ElfLoader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/ElfLoader.java
index 70edb50251..8b042c5de0 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/ElfLoader.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/ElfLoader.java
@@ -23,7 +23,6 @@ import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.bin.format.elf.ElfException;
import ghidra.app.util.bin.format.elf.ElfHeader;
import ghidra.app.util.importer.MessageLog;
-import ghidra.framework.model.DomainFolder;
import ghidra.framework.model.DomainObject;
import ghidra.framework.options.Options;
import ghidra.program.model.lang.Endian;
@@ -153,13 +152,12 @@ public class ElfLoader extends AbstractLibrarySupportLoader {
}
@Override
- protected void postLoadProgramFixups(List importedPrograms, DomainFolder importFolder,
- List options, MessageLog messageLog, TaskMonitor monitor)
- throws CancelledException, IOException {
- super.postLoadProgramFixups(importedPrograms, importFolder, options, messageLog, monitor);
+ protected void postLoadProgramFixups(List loadedPrograms, List options,
+ MessageLog messageLog, TaskMonitor monitor) throws CancelledException, IOException {
+ super.postLoadProgramFixups(loadedPrograms, options, messageLog, monitor);
- for (Program importedProgram : importedPrograms) {
- ELFExternalSymbolResolver.fixUnresolvedExternalSymbols(importedProgram, true,
+ for (LoadedProgram loadedProgram : loadedPrograms) {
+ ELFExternalSymbolResolver.fixUnresolvedExternalSymbols(loadedProgram.program(), true,
messageLog, monitor);
}
}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/IntelHexLoader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/IntelHexLoader.java
index 5a5a641451..2a3fdfcdad 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/IntelHexLoader.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/IntelHexLoader.java
@@ -142,7 +142,7 @@ public class IntelHexLoader extends AbstractProgramLoader {
}
@Override
- protected List loadProgram(ByteProvider provider, String programName,
+ protected List loadProgram(ByteProvider provider, String programName,
DomainFolder programFolder, LoadSpec loadSpec, List options, MessageLog log,
Object consumer, TaskMonitor monitor) throws IOException, CancelledException {
LanguageCompilerSpecPair pair = loadSpec.getLanguageCompilerSpec();
@@ -165,9 +165,9 @@ public class IntelHexLoader extends AbstractProgramLoader {
prog = null;
}
}
- List results = new ArrayList();
+ List results = new ArrayList<>();
if (prog != null) {
- results.add(prog);
+ results.add(new LoadedProgram(prog, programFolder));
}
return results;
}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/MotorolaHexLoader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/MotorolaHexLoader.java
index 5cf55e1cad..63110a61fd 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/MotorolaHexLoader.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/MotorolaHexLoader.java
@@ -160,7 +160,7 @@ public class MotorolaHexLoader extends AbstractProgramLoader {
}
@Override
- protected List loadProgram(ByteProvider provider, String programName,
+ protected List loadProgram(ByteProvider provider, String programName,
DomainFolder programFolder, LoadSpec loadSpec, List options, MessageLog log,
Object consumer, TaskMonitor monitor) throws IOException, CancelledException {
LanguageCompilerSpecPair pair = loadSpec.getLanguageCompilerSpec();
@@ -183,9 +183,9 @@ public class MotorolaHexLoader extends AbstractProgramLoader {
prog = null;
}
}
- List results = new ArrayList();
+ List results = new ArrayList<>();
if (prog != null) {
- results.add(prog);
+ results.add(new LoadedProgram(prog, programFolder));
}
return results;
}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/XmlLoader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/XmlLoader.java
index 0adb052671..8c31f03bea 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/XmlLoader.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/XmlLoader.java
@@ -177,10 +177,10 @@ public class XmlLoader extends AbstractProgramLoader {
}
@Override
- protected List loadProgram(ByteProvider provider, String programName,
+ protected List loadProgram(ByteProvider provider, String programName,
DomainFolder programFolder, LoadSpec loadSpec, List options, MessageLog log,
Object consumer, TaskMonitor monitor) throws IOException, CancelledException {
- List results = new ArrayList<>();
+ List results = new ArrayList<>();
LanguageCompilerSpecPair pair = loadSpec.getLanguageCompilerSpec();
Language importerLanguage = getLanguageService().getLanguage(pair.languageID);
@@ -212,7 +212,7 @@ public class XmlLoader extends AbstractProgramLoader {
}
}
if (prog != null) {
- results.add(prog);
+ results.add(new LoadedProgram(prog, programFolder));
}
return results;
}
diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/app/util/opinion/ApkLoader.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/app/util/opinion/ApkLoader.java
index b3bd1985ca..f296b9c2e9 100644
--- a/Ghidra/Features/FileFormats/src/main/java/ghidra/app/util/opinion/ApkLoader.java
+++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/app/util/opinion/ApkLoader.java
@@ -64,12 +64,12 @@ public class ApkLoader extends DexLoader {
}
@Override
- protected List loadProgram(ByteProvider provider, String programName,
+ protected List loadProgram(ByteProvider provider, String programName,
DomainFolder programFolder, LoadSpec loadSpec, List options, MessageLog log,
Object consumer, TaskMonitor monitor) throws CancelledException, IOException {
boolean success = false;
- List programList = new ArrayList<>();
+ List allLoadedPrograms = new ArrayList<>();
int dexIndex = 1;//DEX file numbering starts at 1
try (ZipFileSystem zipFS = openAPK(provider, monitor)) {
while (!monitor.isCancelled()) {
@@ -86,11 +86,11 @@ public class ApkLoader extends DexLoader {
try (ByteProvider dexProvider =
zipFS.getByteProvider(classesDexFile, monitor)) {
// defer to the super class (DexLoader) to actually load the DEX file
- List program =
+ List loadedPrograms =
super.loadProgram(dexProvider, classesDexFile.getName(), programFolder,
loadSpec, options, log, consumer, monitor);
- programList.addAll(program);
+ allLoadedPrograms.addAll(loadedPrograms);
}
++dexIndex;
}
@@ -101,11 +101,11 @@ public class ApkLoader extends DexLoader {
}
finally {
if (!success) {
- release(programList, consumer);
+ release(allLoadedPrograms, consumer);
}
}
- link(programList, log, monitor);
- return programList;
+ link(allLoadedPrograms.stream().map(e -> e.program()).toList(), log, monitor);
+ return allLoadedPrograms;
}
@Override
diff --git a/Ghidra/RuntimeScripts/Common/support/analyzeHeadlessREADME.html b/Ghidra/RuntimeScripts/Common/support/analyzeHeadlessREADME.html
index 41c4af1f65..6a487c5e8c 100644
--- a/Ghidra/RuntimeScripts/Common/support/analyzeHeadlessREADME.html
+++ b/Ghidra/RuntimeScripts/Common/support/analyzeHeadlessREADME.html
@@ -590,9 +590,12 @@ The Headless Analyzer uses the command-line parameters discussed below. See
-loader-applyLabels <true|false>
-loader-anchorLabels <true|false>
+ -loader-linkExistingProjectLibraries <true|false>
+ -loader-projectLibrarySearchFolder <project path>
-loader-loadLocalLibraries <true|false>
-loader-loadSystemLibraries <true|false>
-loader-libraryLoadDepth <depth>
+ -loader-libraryDestinationFolder <project path>
-loader-applyRelocations <true|false>
-loader-imagebase <imagebase3 >
-loader-dataImageBase <dataImageBase4 >
@@ -602,9 +605,12 @@ The Headless Analyzer uses the command-line parameters discussed below. See
-loader-applyLabels <true|false>
-loader-anchorLabels <true|false>
+ -loader-linkExistingProjectLibraries <true|false>
+ -loader-projectLibrarySearchFolder <project path>
-loader-loadLocalLibraries <true|false>
-loader-loadSystemLibraries <true|false>
-loader-libraryLoadDepth <depth>
+ -loader-libraryDestinationFolder <project path>
-loader-ordinalLookup <true|false>
-loader-parseCliHeaders <true|false>
@@ -612,9 +618,12 @@ The Headless Analyzer uses the command-line parameters discussed below. See
-loader-applyLabels <true|false>
-loader-anchorLabels <true|false>
+ -loader-linkExistingProjectLibraries <true|false>
+ -loader-projectLibrarySearchFolder <project path>
-loader-loadLocalLibraries <true|false>
-loader-loadSystemLibraries <true|false>
-loader-libraryLoadDepth <depth>
+ -loader-libraryDestinationFolder <project path>
-loader-addChainedFixupsRelocations <true|false>