> loaderArgs = new ArrayList<>();
@@ -175,15 +177,12 @@ public class ProgramLoader {
}
/**
- * Sets the suggested project folder path for the {@link Loaded} {@link Program}s. This is
- * just a suggestion, and a {@link Loader} implementation reserves the right to change it
- * for each {@link Loaded} result. The {@link Loaded} results should be queried for their
- * true project folder paths using {@link Loaded#getProjectFolderPath()}.
+ * Sets the project folder path to load into.
*
* The default project folder path is the root of the project ({@code "/"}).
*
- * @param path The suggested project folder path. A {@code null} value will revert the path
- * back to the default value of ({@code "/"}).
+ * @param path The project folder path. A {@code null} value will revert the path back to
+ * the default value of ({@code "/"}).
* @return This {@link Builder}
*/
public Builder projectFolderPath(String path) {
@@ -191,6 +190,21 @@ public class ProgramLoader {
return this;
}
+ /**
+ * Sets whether or not the absolute filesystem path of each {@link Loaded} {@link Program}
+ * should be mirrored in the project, rooted at the specified
+ * {@link #projectFolderPath(String) project folder path}.
+ *
+ * By default, mirroring is off.
+ *
+ * @param shouldMirror True if filesystem mirroring should happen; otherwise, false.
+ * @return This {@link Builder}
+ */
+ public Builder mirror(boolean shouldMirror) {
+ this.mirror = shouldMirror;
+ return this;
+ }
+
/**
* Sets the name to use for the imported {@link Program}.
*
@@ -378,7 +392,7 @@ public class ProgramLoader {
this.compilerSpecId = cspec != null ? cspec.getCompilerSpecID() : null;
return this;
}
-
+
/**
* Sets the {@link MessageLog log} to use during import.
*
@@ -454,8 +468,21 @@ public class ProgramLoader {
LoadSpec loadSpec = getLoadSpec(p);
List loaderOptions = getLoaderOptions(p, loadSpec);
- String importName = importNameOverride != null ? importNameOverride
- : loadSpec.getLoader().getPreferredFileName(p);
+ String importName;
+ if (mirror) {
+ if (importNameOverride != null) {
+ throw new LoadException("Cannot override import name if mirroring");
+ }
+ FSRL f = p.getFSRL();
+ if (f == null) {
+ throw new LoadException("Mirroring requires a file-based source");
+ }
+ importName = f.getPath();
+ }
+ else {
+ importName = importNameOverride != null ? importNameOverride
+ : loadSpec.getLoader().getPreferredFileName(p);
+ }
// Load
Msg.info(ProgramLoader.class, "Using Loader: " + loadSpec.getLoader().getName());
@@ -463,9 +490,11 @@ public class ProgramLoader {
"Using Language/Compiler: " + loadSpec.getLanguageCompilerSpec());
Msg.info(ProgramLoader.class, "Using Library Search Path: " +
Arrays.toString(LibrarySearchPathManager.getLibraryPaths()));
- LoadResults extends DomainObject> loadResults = loadSpec.getLoader()
- .load(p, importName, project, projectFolderPath, loadSpec, loaderOptions,
- log, Objects.requireNonNullElse(consumer, this), monitor);
+ ImporterSettings settings = new ImporterSettings(p, importName, project,
+ projectFolderPath, mirror, loadSpec, loaderOptions,
+ Objects.requireNonNullElse(consumer, this), log, monitor);
+ LoadResults extends DomainObject> loadResults =
+ loadSpec.getLoader().load(settings);
// Optionally echo loader message log to application.log
if (!Loader.loggingDisabled && log.hasMessages()) {
@@ -568,7 +597,8 @@ public class ProgramLoader {
*/
private List getLoaderOptions(ByteProvider p, LoadSpec loadSpec)
throws LanguageNotFoundException, LoadException {
- List options = loadSpec.getLoader().getDefaultOptions(p, loadSpec, null, false);
+ List options =
+ loadSpec.getLoader().getDefaultOptions(p, loadSpec, null, false, mirror);
if (options == null) {
throw new LoadException("Cannot load with null options");
}
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 3e61529179..95ea18face 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
@@ -30,7 +30,6 @@ import ghidra.app.util.importer.*;
import ghidra.formats.gfilesystem.*;
import ghidra.framework.model.*;
import ghidra.plugin.importer.ImporterPlugin;
-import ghidra.program.model.address.Address;
import ghidra.program.model.lang.*;
import ghidra.program.model.listing.Library;
import ghidra.program.model.listing.Program;
@@ -88,6 +87,8 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
public static final String LIBRARY_DEST_FOLDER_OPTION_NAME = "Library Destination Folder";
static final String LIBRARY_DEST_FOLDER_OPTION_DEFAULT = "";
+ public static final String MIRROR_LAYOUT_OPTION_NAME = "Mirror Library Disk Layout";
+
/**
* A hidden option used by the {@link ImporterPlugin}'s "Load Libraries" action to inform this
* {@link Loader} that the {@link Program} to import has already been saved to the project and
@@ -99,17 +100,12 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
/**
* Loads bytes in a particular format into the given {@link Program}.
*
- * @param provider The bytes to load.
- * @param loadSpec The {@link LoadSpec} to use during load.
- * @param options The load options.
* @param program The {@link Program} to load into.
- * @param monitor A cancelable task monitor.
- * @param log The message log.
+ * @param settings The {@link Loader.ImporterSettings}
* @throws IOException if there was an IO-related problem loading.
* @throws CancelledException if the user cancelled the load.
*/
- protected abstract void load(ByteProvider provider, LoadSpec loadSpec, List options,
- Program program, TaskMonitor monitor, MessageLog log)
+ protected abstract void load(Program program, ImporterSettings settings)
throws CancelledException, IOException;
/**
@@ -138,9 +134,7 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
* open {@link Program}.
*/
@Override
- protected List> loadProgram(ByteProvider provider, String loadedName,
- Project project, String projectFolderPath, LoadSpec loadSpec, List options,
- MessageLog log, Object consumer, TaskMonitor monitor)
+ protected List> loadProgram(ImporterSettings settings)
throws CancelledException, IOException {
List> loadedProgramList = new ArrayList<>();
@@ -150,19 +144,18 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
try {
// Load (or get) the primary program
Program program = null;
- if (!shouldLoadOnlyLibraries(options)) {
- program = doLoad(provider, loadedName, loadSpec, libraryNameList, options, consumer,
- log, monitor);
- loadedProgramList.add(
- new Loaded<>(program, loadedName, project, projectFolderPath, consumer));
- log.appendMsg("------------------------------------------------\n");
+ if (!shouldLoadOnlyLibraries(settings)) {
+ program = doLoad(libraryNameList, settings);
+ loadedProgramList.add(new Loaded<>(program, settings));
+ settings.log().appendMsg("------------------------------------------------\n");
}
else {
- if (project == null) {
+ if (settings.project() == null) {
throw new LoadException("Cannot load only libraries...project is null");
}
- DomainFile domainFile =
- project.getProjectData().getFile(projectFolderPath + "/" + loadedName);
+ String projectPath = FSUtilities.appendPath(settings.projectRootPath(),
+ settings.importName());
+ DomainFile domainFile = settings.project().getProjectData().getFile(projectPath);
if (domainFile == null) {
throw new LoadException(
"Cannot load only libraries for a non-existant program");
@@ -170,17 +163,17 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
if (!Program.class.isAssignableFrom(domainFile.getDomainObjectClass())) {
throw new LoadException("Cannot load only libraries for a non-program");
}
- program = (Program) domainFile.getOpenedDomainObject(consumer);
+ program = (Program) domainFile.getOpenedDomainObject(settings.consumer());
if (program == null) {
throw new LoadException("Failed to acquire an open Program");
}
- loadedProgramList.add(new LoadedOpen<>(program, domainFile, consumer));
- libraryNameList.addAll(getLibraryNames(provider, program));
+ loadedProgramList.add(new LoadedOpen<>(program, domainFile,
+ FSRL.fromProgram(program), settings.consumer()));
+ libraryNameList.addAll(getLibraryNames(settings.provider(), program));
}
// Load the libraries
- loadedProgramList.addAll(loadLibraries(provider, program, project, projectFolderPath,
- loadSpec, options, log, consumer, libraryNameList, monitor));
+ loadedProgramList.addAll(loadLibraries(program, libraryNameList, settings));
success = true;
return loadedProgramList;
@@ -193,22 +186,23 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
}
@Override
- protected void loadProgramInto(ByteProvider provider, LoadSpec loadSpec,
- List options, MessageLog log, Program program, TaskMonitor monitor)
+ protected void loadProgramInto(Program program, ImporterSettings settings)
throws CancelledException, LoadException, IOException {
- LanguageCompilerSpecPair pair = loadSpec.getLanguageCompilerSpec();
+ LanguageCompilerSpecPair pair = settings.loadSpec().getLanguageCompilerSpec();
LanguageID languageID = program.getLanguageID();
CompilerSpecID compilerSpecID = program.getCompilerSpec().getCompilerSpecID();
if (!(pair.languageID.equals(languageID) && pair.compilerSpecID.equals(compilerSpecID))) {
- String message = provider.getAbsolutePath() +
+ String message = settings.provider().getAbsolutePath() +
" does not have the same language/compiler spec as program " + program.getName();
- log.appendMsg(message);
+ settings.log().appendMsg(message);
throw new LoadException(message);
}
- log.appendMsg("Loading " + provider.getAbsolutePath() + "...");
- load(provider, loadSpec, options, program, monitor, log);
- log.appendMsg("--------------------------------------------------------------------\n");
+ settings.log().appendMsg("Loading " + settings.provider().getAbsolutePath() + "...");
+ load(program, settings);
+ settings.log()
+ .appendMsg(
+ "--------------------------------------------------------------------\n");
}
/**
@@ -217,21 +211,22 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
* Fix up program's external library entries so that they point to a path in the project.
*/
@Override
- protected void postLoadProgramFixups(List> loadedPrograms, Project project,
- LoadSpec loadSpec, List options, MessageLog log, TaskMonitor monitor)
- throws CancelledException, IOException {
+ protected void postLoadProgramFixups(List> loadedPrograms,
+ ImporterSettings settings) throws CancelledException, IOException {
+
+ TaskMonitor monitor = settings.monitor();
+
if (loadedPrograms.isEmpty() ||
- (!isLinkExistingLibraries(options) && !isLoadLibraries(options))) {
+ (!isLinkExistingLibraries(settings) && !isLoadLibraries(settings))) {
return;
}
- List searchFolders =
- getLibrarySearchFolders(loadedPrograms, project, options, log);
+ List searchFolders = getLibrarySearchFolders(loadedPrograms, settings);
Program firstProgram = loadedPrograms.getFirst().getDomainObject(this);
List searchPaths;
try {
- searchPaths = getLibrarySearchPaths(firstProgram, loadSpec, options, log, monitor);
+ searchPaths = getLibrarySearchPaths(firstProgram, settings);
}
finally {
firstProgram.release(this);
@@ -259,7 +254,7 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
int id = program.startTransaction("Resolving external references");
try {
resolveExternalLibraries(program, saveablePrograms, searchFolders, searchPaths,
- options, monitor, log);
+ settings);
}
finally {
program.endTransaction(id, true);
@@ -283,21 +278,23 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
@Override
public List getDefaultOptions(ByteProvider provider, LoadSpec loadSpec,
- DomainObject domainObject, boolean loadIntoProgram) {
- List list =
- super.getDefaultOptions(provider, loadSpec, domainObject, loadIntoProgram);
+ DomainObject domainObject, boolean loadIntoProgram, boolean mirrorFsLayout) {
+ List list = super.getDefaultOptions(provider, loadSpec, domainObject,
+ loadIntoProgram, mirrorFsLayout);
list.add(new Option(LINK_EXISTING_OPTION_NAME, LINK_EXISTING_OPTION_DEFAULT, Boolean.class,
Loader.COMMAND_LINE_ARG_PREFIX + "-linkExistingProjectLibraries"));
list.add(new DomainFolderOption(LINK_SEARCH_FOLDER_OPTION_NAME,
- Loader.COMMAND_LINE_ARG_PREFIX + "-projectLibrarySearchFolder"));
+ Loader.COMMAND_LINE_ARG_PREFIX + "-projectLibrarySearchFolder", mirrorFsLayout));
list.add(new Option(LOAD_LIBRARY_OPTION_NAME, LOAD_LIBRARY_OPTION_DEFAULT, Boolean.class,
Loader.COMMAND_LINE_ARG_PREFIX + "-loadLibraries"));
list.add(new LibrarySearchPathDummyOption(LIBRARY_SEARCH_PATH_DUMMY_OPTION_NAME));
list.add(new Option(DEPTH_OPTION_NAME, DEPTH_OPTION_DEFAULT, Integer.class,
Loader.COMMAND_LINE_ARG_PREFIX + "-libraryLoadDepth"));
list.add(new DomainFolderOption(LIBRARY_DEST_FOLDER_OPTION_NAME,
- Loader.COMMAND_LINE_ARG_PREFIX + "-libraryDestinationFolder"));
+ Loader.COMMAND_LINE_ARG_PREFIX + "-libraryDestinationFolder", mirrorFsLayout));
+ list.add(new Option(MIRROR_LAYOUT_OPTION_NAME, Boolean.class, mirrorFsLayout,
+ Loader.COMMAND_LINE_ARG_PREFIX + "-libraryMirrorLayout", null, null, mirrorFsLayout));
list.add(new Option(LOAD_ONLY_LIBRARIES_OPTION_NAME, Boolean.class,
LOAD_ONLY_LIBRARIES_OPTION_DEFAULT,
Loader.COMMAND_LINE_ARG_PREFIX + "-loadOnlyLibraries", null, null, true));
@@ -313,6 +310,7 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
String name = option.getName();
if (name.equals(LINK_EXISTING_OPTION_NAME) ||
name.equals(LOAD_LIBRARY_OPTION_NAME) ||
+ name.equals(MIRROR_LAYOUT_OPTION_NAME) ||
name.equals(LOAD_ONLY_LIBRARIES_OPTION_NAME)) {
if (!Boolean.class.isAssignableFrom(option.getValueClass())) {
return "Invalid type for option: " + name + " - " + option.getValueClass();
@@ -342,39 +340,41 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
/**
* Checks to see if existing libraries should be linked
*
- * @param options a {@link List} of {@link Option}s
+ * @param settings The {@link Loader.ImporterSettings}
* @return True if existing libraries should be linked; otherwise, false
*/
- protected boolean isLinkExistingLibraries(List options) {
- return OptionUtils.getOption(LINK_EXISTING_OPTION_NAME, options,
+ protected boolean isLinkExistingLibraries(ImporterSettings settings) {
+ return OptionUtils.getOption(LINK_EXISTING_OPTION_NAME, settings.options(),
LINK_EXISTING_OPTION_DEFAULT);
}
/**
* Gets the {@link DomainFolder project folder} to search for existing libraries
*
- * @param project The {@link Project}. Could be null if there is no project.
* @param program The {@link Program} being loaded
- * @param projectFolderPath The project folder path the program will get saved to. Could be null
- * if the program is not getting saved to the project.
- * @param options a {@link List} of {@link Option}s
- * @param log The log
+ * @param settings The {@link Loader.ImporterSettings}
* @return The path of the project folder to search for existing libraries, or null if no
* project folders can be or should be searched
*/
- protected DomainFolder getLinkSearchFolder(Project project, Program program,
- String projectFolderPath, List options, MessageLog log) {
- if (!shouldSearchAllPaths(program, options, log) && !isLinkExistingLibraries(options)) {
+ protected DomainFolder getLinkSearchFolder(Program program, ImporterSettings settings) {
+ if (!shouldSearchAllPaths(program, settings) && !isLinkExistingLibraries(settings)) {
return null;
}
- if (project == null) {
+ if (settings.project() == null) {
return null;
}
- String linkSearchFolderPath = OptionUtils.getOption(LINK_SEARCH_FOLDER_OPTION_NAME, options,
- LINK_SEARCH_FOLDER_OPTION_DEFAULT);
+ ProjectData projectData = settings.project().getProjectData();
+ if (settings.mirrorFsLayout()) {
+ return projectData.getFolder(settings.projectRootPath());
+ }
+
+ String linkSearchFolderPath = OptionUtils.getOption(LINK_SEARCH_FOLDER_OPTION_NAME,
+ settings.options(), LINK_SEARCH_FOLDER_OPTION_DEFAULT);
+
+ String projectFolderPath = FSUtilities.appendPath(settings.projectRootPath(),
+ settings.importPathOnly());
- ProjectData projectData = project.getProjectData();
if (linkSearchFolderPath.isBlank()) {
if (projectFolderPath == null) {
return null;
@@ -388,57 +388,68 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
/**
* Checks to see if libraries from disk should be loaded
*
- * @param options a {@link List} of {@link Option}s
+ * @param settings The {@link Loader.ImporterSettings}
* @return True if libraries from disk should be loaded; otherwise, false
*/
- protected boolean isLoadLibraries(List options) {
- return OptionUtils.getOption(LOAD_LIBRARY_OPTION_NAME, options,
+ protected boolean isLoadLibraries(ImporterSettings settings) {
+ return OptionUtils.getOption(LOAD_LIBRARY_OPTION_NAME, settings.options(),
LOAD_LIBRARY_OPTION_DEFAULT);
}
+ /**
+ * Checks to see if library organization mirrors filesystem layout
+ *
+ * @param settings The {@link Loader.ImporterSettings}
+ * @return True if library organization mirrors filesystem layout; false if flat layout
+ */
+ protected boolean isMirroredLayout(ImporterSettings settings) {
+ return OptionUtils.getOption(MIRROR_LAYOUT_OPTION_NAME, settings.options(),
+ settings.mirrorFsLayout());
+ }
+
/**
* Checks to see if only libraries should be loaded (i.e., not the main program)
*
- * @param options a {@link List} of {@link Option}s
+ * @param settings The {@link Loader.ImporterSettings}
* @return True if only libraries should be loaded; otherwise, false
*/
- protected boolean shouldLoadOnlyLibraries(List options) {
- return OptionUtils.getOption(LOAD_ONLY_LIBRARIES_OPTION_NAME, options,
+ protected boolean shouldLoadOnlyLibraries(ImporterSettings settings) {
+ return OptionUtils.getOption(LOAD_ONLY_LIBRARIES_OPTION_NAME, settings.options(),
LOAD_ONLY_LIBRARIES_OPTION_DEFAULT);
}
/**
* Gets the desired recursive library load depth
*
- * @param options a {@link List} of {@link Option}s
+ * @param settings The {@link Loader.ImporterSettings}
* @return The desired recursive library load depth
*/
- protected int getLibraryLoadDepth(List options) {
- return OptionUtils.getOption(DEPTH_OPTION_NAME, options, DEPTH_OPTION_DEFAULT);
+ protected int getLibraryLoadDepth(ImporterSettings settings) {
+ return OptionUtils.getOption(DEPTH_OPTION_NAME, settings.options(), DEPTH_OPTION_DEFAULT);
}
/**
* Gets the project folder path to load the libraries into. It does not have to exist in the
* project yet.
*
- * @param project The {@link Project}. Could be null if there is no project.
- * @param projectFolderPath The project folder path the program will get saved to. Could be null
- * if the program is not getting saved to the project.
- * @param options a {@link List} of {@link Option}s
+ * @param settings The {@link Loader.ImporterSettings}
* @return The path of the project folder to load the libraries into. Could be null if the
* specified project is null or a destination folder path could not be determined.
*/
- protected String getLibraryDestinationFolderPath(Project project, String projectFolderPath,
- List options) {
- if (project == null) {
+ protected String getLibraryDestinationFolderPath(ImporterSettings settings) {
+ if (settings.project() == null) {
return null;
}
+ if (settings.mirrorFsLayout()) {
+ return settings.projectRootPath();
+ }
+
String libraryDestinationFolderPath = OptionUtils.getOption(LIBRARY_DEST_FOLDER_OPTION_NAME,
- options, LIBRARY_DEST_FOLDER_OPTION_DEFAULT);
+ settings.options(), LIBRARY_DEST_FOLDER_OPTION_DEFAULT);
if (libraryDestinationFolderPath.isBlank()) {
- return projectFolderPath;
+ return settings.projectRootPath();
}
return FilenameUtils.separatorsToUnix(libraryDestinationFolderPath);
@@ -449,22 +460,21 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
* existing libraries. It will only be returned if the options to load new libraries into the
* project are set.
*
- * @param project The {@link Project}. Could be null if there is no project.
* @param libraryDestinationFolderPath The path of the project folder to load the libraries
* into. Could be null (@see #getLibraryDestinationFolderPath(Project, String, List)).
- * @param options a {@link List} of {@link Option}s
+ * @param settings The {@link Loader.ImporterSettings}
* @return The path of the destination project folder to search for existing libraries, or null
* if the destination folder is not being used or should not be searched
*/
- protected DomainFolder getLibraryDestinationSearchFolder(Project project,
- String libraryDestinationFolderPath, List options) {
- if (project == null || libraryDestinationFolderPath == null) {
+ protected DomainFolder getLibraryDestinationSearchFolder(String libraryDestinationFolderPath,
+ ImporterSettings settings) {
+ if (settings.project() == null || libraryDestinationFolderPath == null) {
return null;
}
- if (!isLoadLibraries(options)) {
+ if (!isLoadLibraries(settings)) {
return null;
}
- return project.getProjectData().getFolder(libraryDestinationFolderPath);
+ return settings.project().getProjectData().getFolder(libraryDestinationFolderPath);
}
/**
@@ -472,23 +482,18 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
* options
*
* @param loadedPrograms the list of {@link Loaded} {@link Program}s
- * @param project The {@link Project} to load into. Could be null if there is no project.
- * @param options The {@link List} of {@link Option}s
- * @param log The log
+ * @param settings The {@link Loader.ImporterSettings}
* @return A {@link List} of library search {@link DomainFolder folders} based on the current
* options
*/
protected List getLibrarySearchFolders(List> loadedPrograms,
- Project project, List options, MessageLog log) {
+ ImporterSettings settings) {
Program firstProgram = loadedPrograms.getFirst().getDomainObject(this);
try {
List searchFolders = new ArrayList<>();
- String projectFolderPath = loadedPrograms.get(0).getProjectFolderPath();
- String destPath = getLibraryDestinationFolderPath(project, projectFolderPath, options);
- DomainFolder destSearchFolder =
- getLibraryDestinationSearchFolder(project, destPath, options);
- DomainFolder linkSearchFolder =
- getLinkSearchFolder(project, firstProgram, projectFolderPath, options, log);
+ String destPath = getLibraryDestinationFolderPath(settings);
+ DomainFolder destSearchFolder = getLibraryDestinationSearchFolder(destPath, settings);
+ DomainFolder linkSearchFolder = getLinkSearchFolder(firstProgram, settings);
Optional.ofNullable(destSearchFolder).ifPresent(searchFolders::add);
Optional.ofNullable(linkSearchFolder).ifPresent(searchFolders::add);
return searchFolders;
@@ -503,36 +508,10 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
* of what options are set
*
* @param program The {@link Program} being loaded
- * @param options a {@link List} of {@link Option}s
- * @param log The log
+ * @param settings The {@link Loader.ImporterSettings}
* @return True if all possible search paths should be used, regardless of what options are set
*/
- protected boolean shouldSearchAllPaths(Program program, List options, MessageLog log) {
- return false;
- }
-
- /**
- * Specifies if the library filenames specified by this loader should be exact case match
- * or case-insensitive.
- *
- * Derived loader classes should override this method and specify if the OS that normally
- * handles this type of binary is case-insensitive.
- *
- * @return True if case-insensitive or false if case-sensitive.
- */
- protected boolean isCaseInsensitiveLibraryFilenames() {
- return false;
- }
-
- /**
- * Specifies if this loader can refer to library filenames without filename extensions.
- *
- * Derived loader classes should override this method if library filename extensions are
- * optional. If they are required, there is no need to override this method.
- *
- * @return True if library filename extensions are optional; otherwise, false
- */
- protected boolean isOptionalLibraryFilenameExtensions() {
+ protected boolean shouldSearchAllPaths(Program program, ImporterSettings settings) {
return false;
}
@@ -559,19 +538,14 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
* @param library The loaded library {@link Program}
* @param libraryName The name of the library
* @param libraryFsrl The library {@link FSRL}
- * @param provider The library bytes
* @param unprocessed The {@link Queue} of {@link UnprocessedLibrary unprocessed libraries}
* @param depth The load depth of the library to load
- * @param loadSpec The {@link LoadSpec} used for the load
- * @param options The options
- * @param log The log
- * @param monitor A cancelable monitor
+ * @param settings The {@link Loader.ImporterSettings}
* @throws IOException If an IO-related error occurred
* @throws CancelledException If the user cancelled the action
*/
protected void processLibrary(Program library, String libraryName, FSRL libraryFsrl,
- ByteProvider provider, Queue unprocessed, int depth,
- LoadSpec loadSpec, List options, MessageLog log, TaskMonitor monitor)
+ Queue unprocessed, int depth, ImporterSettings settings)
throws IOException, CancelledException {
// Default behavior is to do nothing
}
@@ -579,40 +553,32 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
/**
* Loads the given list of libraries as {@link Loaded} {@link Program}s
*
- * @param provider The {@link ByteProvider} of the program being loaded
* @param program The {@link Program} being loaded
- * @param project The {@link Project}. Could be null if there is no project.
- * @param projectFolderPath The project folder path the program will get saved to. Could be null
- * if the program is not getting saved to the project.
- * @param desiredLoadSpec The desired {@link LoadSpec}
- * @param options The load options
- * @param log The log
- * @param consumer A consumer object for {@link DomainObject}s generated
- * @param monitor A cancelable task monitor
- * @return A {@link List} of newly loaded programs and libraries. Any program in the list is
+ * @param libraryNameList The {@link List} of libraries to load
+ * @param settings The {@link Loader.ImporterSettings}
+ * @return A {@link List} of newly loaded programs and libraries. Any program in the list is
* the caller's responsibility to release.
* @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,
- Project project, String projectFolderPath, LoadSpec desiredLoadSpec,
- List options, MessageLog log, Object consumer, List libraryNameList,
- TaskMonitor monitor) throws CancelledException, IOException {
+ private List> loadLibraries(Program program, List libraryNameList,
+ ImporterSettings settings) throws CancelledException, IOException {
+ ByteProvider provider = settings.provider();
+ List options = settings.options();
+ MessageLog log = settings.log();
+ TaskMonitor monitor = settings.monitor();
List> loadedPrograms = new ArrayList<>();
Set processed = new TreeSet<>(getLibraryNameComparator());
- Queue unprocessed = createUnprocessedQueue(libraryNameList, options);
- boolean loadLibraries = isLoadLibraries(options);
+ Queue unprocessed = createUnprocessedQueue(libraryNameList, settings);
+ boolean loadLibraries = isLoadLibraries(settings);
List customSearchPaths =
getCustomLibrarySearchPaths(provider, options, log, monitor);
- List searchPaths =
- getLibrarySearchPaths(program, desiredLoadSpec, options, log, monitor);
- DomainFolder linkSearchFolder =
- getLinkSearchFolder(project, program, projectFolderPath, options, log);
- String libraryDestFolderPath =
- getLibraryDestinationFolderPath(project, projectFolderPath, options);
+ List searchPaths = getLibrarySearchPaths(program, settings);
+ DomainFolder linkSearchFolder = getLinkSearchFolder(program, settings);
+ String libraryDestFolderPath = getLibraryDestinationFolderPath(settings);
DomainFolder libraryDestFolder =
- getLibraryDestinationSearchFolder(project, libraryDestFolderPath, options);
+ getLibraryDestinationSearchFolder(libraryDestFolderPath, settings);
boolean success = false;
try {
@@ -625,24 +591,22 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
continue;
}
processed.add(library);
- if (findLibraryInProject(library, libraryDestFolder, searchPaths, options,
- monitor) != null) {
+ if (findLibraryInProject(library, libraryDestFolder, searchPaths,
+ settings) != null) {
log.appendMsg("Found %s in %s...".formatted(library, libraryDestFolder));
log.appendMsg("------------------------------------------------\n");
}
- else if (findLibraryInProject(library, linkSearchFolder, searchPaths, options,
- monitor) != null) {
+ else if (findLibraryInProject(library, linkSearchFolder, searchPaths,
+ settings) != null) {
log.appendMsg("Found %s in %s...".formatted(library, linkSearchFolder));
log.appendMsg("------------------------------------------------\n");
}
- else if (isLoadLibraries(options) || shouldSearchAllPaths(program, options, log)) {
- Loaded loadedLibrary = loadLibraryFromSearchPaths(library, provider,
- project, customSearchPaths, libraryDestFolderPath, unprocessed, depth,
- desiredLoadSpec, options, log, consumer, monitor);
+ else if (isLoadLibraries(settings) || shouldSearchAllPaths(program, settings)) {
+ Loaded loadedLibrary = loadLibraryFromSearchPaths(library,
+ customSearchPaths, libraryDestFolderPath, unprocessed, depth, settings);
if (loadedLibrary == null) {
- loadedLibrary = loadLibraryFromSearchPaths(library, provider, project,
- searchPaths, libraryDestFolderPath, unprocessed, depth, desiredLoadSpec,
- options, log, consumer, monitor);
+ loadedLibrary = loadLibraryFromSearchPaths(library, searchPaths,
+ libraryDestFolderPath, unprocessed, depth, settings);
}
if (loadedLibrary != null) {
boolean temporary = !loadLibraries || unprocessedLibrary.temporary();
@@ -677,72 +641,76 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
* the given {@link List} of search paths
*
* @param library The library to load
- * @param provider The {@link ByteProvider} of the program being loaded
- * @param project The {@link Project}. Could be null if there is no project.
* @param searchPaths A {@link List} of {@link LibrarySearchPath}s that will be searched
- * @param libraryDestFolderPath The path of the project folder to load the libraries into.
- * Could be null if the specified project is null or a destination folder path could not be
- * determined.
* @param unprocessed The {@link Queue} of {@link UnprocessedLibrary unprocessed libraries}
* @param depth The load depth of the library to load
- * @param desiredLoadSpec The desired {@link LoadSpec}
- * @param options The load options
- * @param log The log
- * @param consumer A consumer object for {@link DomainObject}s generated
- * @param monitor A cancelable task monitor
+ * @param settings The {@link Loader.ImporterSettings}
* @return The {@link Loaded} library, or null if it was not found. The returned library is the
* caller's responsibility to release.
* @throws IOException if there was an IO-related problem loading
* @throws CancelledException if the user cancelled the load
*/
- private Loaded loadLibraryFromSearchPaths(String library, ByteProvider provider,
- Project project, List searchPaths, String libraryDestFolderPath,
- Queue unprocessed, int depth, LoadSpec desiredLoadSpec,
- List options, MessageLog log, Object consumer, TaskMonitor monitor)
+ private Loaded loadLibraryFromSearchPaths(String library,
+ List searchPaths, String libraryDestFolderPath,
+ Queue unprocessed, int depth, ImporterSettings settings)
throws CancelledException, IOException {
if (searchPaths.isEmpty()) {
return null;
}
+ List options = settings.options();
+ LoadSpec desiredLoadSpec = settings.loadSpec();
+ Object consumer = settings.consumer();
+ MessageLog log = settings.log();
+ TaskMonitor monitor = settings.monitor();
+
log.appendMsg("Searching %d path%s for library %s...".formatted(searchPaths.size(),
searchPaths.size() > 1 ? "s" : "", library));
Program libraryProgram = null;
- String simpleLibraryName = FilenameUtils.getName(library);
- boolean isAbsolute = isAbsoluteLibraryPath(library);
boolean success = false;
try {
- List candidateLibraryFsrls =
+ List candidateLibraryFiles =
findLibraryOnDisk(library, searchPaths, log, monitor);
- if (candidateLibraryFsrls.isEmpty()) {
+ if (candidateLibraryFiles.isEmpty()) {
log.appendMsg("Library not found.");
return null;
}
- for (FSRL candidateLibraryFsrl : candidateLibraryFsrls) {
+ for (GFile candidateLibraryFile : candidateLibraryFiles) {
monitor.checkCancelled();
+ FSRL candidateLibraryFsrl = candidateLibraryFile.getFSRL();
List newLibraryList = new ArrayList<>();
- libraryProgram = loadLibrary(simpleLibraryName, candidateLibraryFsrl,
- desiredLoadSpec, newLibraryList, options, consumer, log, monitor);
- for (String newLibraryName : newLibraryList) {
- unprocessed.add(new UnprocessedLibrary(newLibraryName, depth - 1, false));
- }
- if (libraryProgram == null) {
- continue;
- }
- processLibrary(libraryProgram, library, candidateLibraryFsrl, provider, unprocessed,
- depth, desiredLoadSpec, options, log, monitor);
- success = true;
- String folderPath = libraryDestFolderPath;
- if (folderPath != null) {
- if (isAbsolute) {
- folderPath = joinPaths(folderPath, FilenameUtils.getFullPath(library));
+
+ try (ByteProvider provider = createLibraryByteProvider(candidateLibraryFsrl,
+ desiredLoadSpec, log, monitor)) {
+ LoadSpec libLoadSpec = matchSupportedLoadSpec(settings.loadSpec(), provider);
+ if (libLoadSpec == null) {
+ log.appendMsg("Skipping library which is the wrong architecture: " +
+ candidateLibraryFsrl);
+ continue;
}
+ if (isMirroredLayout(settings)) {
+ library = FSUtilities.mirroredProjectPath(candidateLibraryFsrl.getPath());
+ }
+ ImporterSettings librarySettings =
+ new ImporterSettings(provider, library, settings.project(),
+ libraryDestFolderPath, isMirroredLayout(settings), libLoadSpec, options,
+ consumer, log, monitor);
+ libraryProgram = doLoad(newLibraryList, librarySettings);
+ for (String newLibraryName : newLibraryList) {
+ unprocessed.add(new UnprocessedLibrary(newLibraryName, depth - 1, false));
+ }
+ if (libraryProgram == null) {
+ continue;
+ }
+ processLibrary(libraryProgram, library, candidateLibraryFsrl, unprocessed,
+ depth, librarySettings);
+ success = true;
+ return new Loaded(libraryProgram, librarySettings);
}
- return new Loaded(libraryProgram, simpleLibraryName, project, folderPath,
- consumer);
}
}
finally {
@@ -754,8 +722,7 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
}
/**
- * Find the library within the specified {@link DomainFolder root search folder}. This method
- * will handle relative path normalization.
+ * Find the library within the specified {@link DomainFolder}.
*
* If the library path is a simple name without any path separators, only the given folder
* will be searched.
@@ -766,54 +733,79 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
* If the library path has a path and it wasn't found under the given folder, the
* filename part of library path will be used to search the given folder for matches.
*
- * @param library library to find
- * @param rootSearchFolder {@link DomainFolder root folder} within which imported libraries will
+ * @param library The library to find. Depending on the type of library, this could be a simple
+ * filename or an absolute path.
+ * @param folder {@link DomainFolder root folder} within which imported libraries will
* be searched. If null this method will return null.
* @param searchPaths A {@link List} of {@link LibrarySearchPath}s that will be searched
- * @param options The load options
- * @param monitor A cancelable task monitor
+ * @param settings The {@link Loader.ImporterSettings}
* @return The found {@link DomainFile} or null if not found
* @throws CancelledException if the user cancelled the load
*/
- protected DomainFile findLibraryInProject(String library, DomainFolder rootSearchFolder,
- List searchPaths, List options, TaskMonitor monitor)
+ protected DomainFile findLibraryInProject(String library, DomainFolder folder,
+ List searchPaths, ImporterSettings settings)
throws CancelledException {
- if (rootSearchFolder == null) {
+ if (folder == null) {
return null;
}
- // Lookup by full project path
- // NOTE: probably no need to support optional extensions and case-insensitivity for this case
- String projectPath = joinPaths(rootSearchFolder.getPathname(), library);
- ProjectData projectData = rootSearchFolder.getProjectData();
- DomainFile ret = projectData.getFile(projectPath);
- if (ret != null) {
- return ret;
+ ProjectData projectData = folder.getProjectData();
+
+ if (isMirroredLayout(settings)) {
+ // Perform the lookup based on file system search path layout within the project
+ for (LibrarySearchPath searchPath : searchPaths) {
+ settings.monitor().checkCancelled();
+
+ GFileSystem fs = searchPath.fsRef().getFilesystem();
+ String fsPath = FSUtilities.mirroredProjectPath(
+ FSUtilities.appendPath(fs.getFSRL().getPath(), searchPath.relativeFsPath()));
+ String projectPath =
+ FSUtilities.appendPath(folder.getPathname(), fsPath, library);
+ DomainFolder parentFolder =
+ projectData.getFolder(FilenameUtils.getFullPath(projectPath));
+ if (parentFolder == null) {
+ continue;
+ }
+ DomainFile ret =
+ lookupLibraryInFolder(FilenameUtils.getName(library), parentFolder);
+ if (ret != null) {
+ return ret;
+ }
+ }
+ return null;
+ }
+
+ if (isAbsoluteLibraryPath(library)) {
+ String parentProjectPath =
+ FSUtilities.appendPath(folder.getPathname(), FilenameUtils.getFullPath(library));
+ folder = projectData.getFolder(parentProjectPath);
+ if (folder == null) {
+ return null;
+ }
}
- // Quick lookup by library filename (ignoring full library path) in given folder.
- // We try this first to hopefully avoid needing to iterate over the files in the folder
- // factoring in case and extensions
String libraryName = FilenameUtils.getName(library);
- if ((ret = rootSearchFolder.getFile(libraryName)) != null) {
- return ret;
+ DomainFile file = folder.getFile(libraryName);
+ if (file != null) {
+ return file;
}
- // Factoring in case and optional file extensions, iterate over given folder looking for
- // a match
- boolean noExtension = FilenameUtils.getExtension(libraryName).equals("");
- Comparator comparator = getLibraryNameComparator();
- for (DomainFile file : rootSearchFolder.getFiles()) {
- String candidateName = file.getName();
- if (isOptionalLibraryFilenameExtensions() && noExtension) {
- candidateName = FilenameUtils.getBaseName(candidateName);
- }
- if (comparator.compare(candidateName, libraryName) == 0) {
- return file;
- }
- }
+ return lookupLibraryInFolder(libraryName, folder);
+ }
- return null;
+ /**
+ * Looks in the given {@link DomainFolder} for the given name using the loader's
+ * {@link #getLibraryNameComparator() library name comparator}
+ *
+ * @param libraryName The library name to search for (no path included)
+ * @param folder The {@link DomainFolder} to search in
+ * @return A matching library {@link DomainFile}, or {@code null} if one was not found
+ */
+ protected DomainFile lookupLibraryInFolder(String libraryName, DomainFolder folder) {
+ return Arrays.stream(folder.getFiles())
+ .filter(df -> getLibraryNameComparator().compare(df.getName(), libraryName) == 0)
+ .findFirst()
+ .orElse(null);
}
/**
@@ -822,10 +814,6 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
*
* Each search path directory will be searched for the library file in order.
*
- * If the library file specifies a path, it is treated as a relative subdirectory of
- * each search path directory that is searched, and if not found, the filename part of
- * the library is used to search just the search path directory.
- *
* If the library specifies an absolute path, its native path is also searched on the local
* filesystem.
*
@@ -837,10 +825,10 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
* @return A {@link List} of {@link GFile files} that match the requested library path
* @throws CancelledException if the user cancelled the operation
*/
- private List findLibraryOnDisk(String library, List searchPaths,
+ private List findLibraryOnDisk(String library, List searchPaths,
MessageLog log, TaskMonitor monitor) throws CancelledException {
- List results = new ArrayList<>();
+ List results = new ArrayList<>();
try {
for (LibrarySearchPath searchPath : searchPaths) {
@@ -848,14 +836,14 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
String fullLibraryPath =
FSUtilities.appendPath(searchPath.relativeFsPath(), library);
GFileSystem fs = searchPath.fsRef().getFilesystem();
- FSRL fsrl = resolveLibraryFile(fs, fullLibraryPath);
- Optional.ofNullable(fsrl).ifPresent(results::add);
+ GFile file = lookupLibraryInFs(fullLibraryPath, fs);
+ Optional.ofNullable(file).ifPresent(results::add);
}
if (results.isEmpty() && isAbsoluteLibraryPath(library)) {
LocalFileSystem localFS = FileSystemService.getInstance().getLocalFS();
- FSRL fsrl = resolveLibraryFile(localFS, library);
- Optional.ofNullable(fsrl).ifPresent(results::add);
+ GFile file = lookupLibraryInFs(library, localFS);
+ Optional.ofNullable(file).ifPresent(results::add);
}
}
catch (IOException e) {
@@ -865,87 +853,36 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
return results;
}
- /**
- * Imports a library file into a ghidra project. Use this method if you already have
- * a {@link ByteProvider} available.
- *
- * @param libraryName The name of the library to load
- * @param libraryFsrl The library {@link FSRL} to load
- * @param desiredLoadSpec The desired {@link LoadSpec}
- * @param libraryNameList A {@link List} to be populated with the given library's dependent
- * library names
- * @param options The load options
- * @param log The log
- * @param consumer A consumer object for {@link DomainObject}s generated
- * @param monitor A cancelable task monitor
- * @return The loaded {@link Program}, or null if the load was not successful
- * @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, FSRL libraryFsrl, LoadSpec desiredLoadSpec,
- List libraryNameList, List options, Object consumer, MessageLog log,
- TaskMonitor monitor) throws CancelledException, IOException {
-
- try (ByteProvider provider =
- createLibraryByteProvider(libraryFsrl, desiredLoadSpec, log, monitor)) {
-
- LoadSpec libLoadSpec = matchSupportedLoadSpec(desiredLoadSpec, provider);
- if (libLoadSpec == null) {
- log.appendMsg("Skipping library which is the wrong architecture: " + libraryFsrl);
- return null;
- }
-
- return doLoad(provider, libraryName, libLoadSpec, libraryNameList,
- options, consumer, log, monitor);
- }
- }
-
/**
* Loads the given provider
*
- * @param provider The {@link ByteProvider} to load
- * @param programName The name of the new program
- * @param loadSpec The {@link LoadSpec}
* @param libraryNameList A {@link List} to be populated with the loaded program's dependent
* library names
- * @param options The load options
- * @param log The log
- * @param consumer A consumer object for {@link DomainObject}s generated
- * @param monitor A cancelable task monitor
+ * @param settings The {@link Loader.ImporterSettings}
* @return The newly loaded {@link Program}
* @throws CancelledException if the user cancelled the load operation
* @throws IOException if there was an IO-related error during the load
*/
- private Program doLoad(ByteProvider provider, String programName, LoadSpec loadSpec,
- List libraryNameList, List options, Object consumer, MessageLog log,
- TaskMonitor monitor) throws CancelledException, IOException {
- LanguageCompilerSpecPair pair = loadSpec.getLanguageCompilerSpec();
- Language language = getLanguageService().getLanguage(pair.languageID);
- CompilerSpec compilerSpec = language.getCompilerSpecByID(pair.compilerSpecID);
+ private Program doLoad(List libraryNameList, ImporterSettings settings)
+ throws CancelledException, IOException {
+ MessageLog log = settings.log();
- monitor.setMessage(provider.getName());
-
- Address imageBaseAddr = language.getAddressFactory()
- .getDefaultAddressSpace()
- .getAddress(
- loadSpec.getDesiredImageBase());
- Program program = createProgram(provider, programName, imageBaseAddr, getName(), language,
- compilerSpec, consumer);
+ Program program = createProgram(settings);
int transactionID = program.startTransaction("Loading");
boolean success = false;
try {
- log.appendMsg("Loading %s...".formatted(provider.getFSRL()));
- load(provider, loadSpec, options, program, monitor, log);
- createDefaultMemoryBlocks(program, language, log);
- libraryNameList.addAll(getLibraryNames(provider, program));
+ log.appendMsg("Loading %s...".formatted(settings.provider().getFSRL()));
+ load(program, settings);
+ createDefaultMemoryBlocks(program, settings);
+ libraryNameList.addAll(getLibraryNames(settings.provider(), program));
success = true;
return program;
}
finally {
program.endTransaction(transactionID, true); // More efficient to commit when program will be discarded
if (!success) {
- program.release(consumer);
+ program.release(settings.consumer());
}
}
}
@@ -978,9 +915,10 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
private void resolveExternalLibraries(Program program, List> loadedPrograms,
List searchFolders, List fsSearchPaths,
- List options, TaskMonitor monitor, MessageLog log)
- throws CancelledException {
+ ImporterSettings settings) throws CancelledException {
ExternalManager extManager = program.getExternalManager();
+ MessageLog log = settings.log();
+ TaskMonitor monitor = settings.monitor();
String[] extLibNames = extManager.getExternalLibraryNames();
log.appendMsg("Linking the External Programs of '%s' to imported libraries..."
.formatted(program.getName()));
@@ -992,15 +930,16 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
try {
Loaded match = findLibraryInLoadedList(loadedPrograms, externalLibName);
if (match != null) {
- String path = match.getProjectFolderPath() + match.getName();
- extManager.setExternalPath(externalLibName, path, false);
- log.appendMsg(" [" + externalLibName + "] -> [" + path + "]");
+ extManager.setExternalPath(externalLibName, FSUtilities
+ .appendPath(match.getProjectFolderPath(), match.getName()),
+ false);
+ log.appendMsg(" [" + externalLibName + "] -> [" + match.getName() + "]");
}
else {
boolean found = false;
for (DomainFolder searchFolder : searchFolders) {
DomainFile alreadyImportedLib = findLibraryInProject(externalLibName,
- searchFolder, fsSearchPaths, options, monitor);
+ searchFolder, fsSearchPaths, settings);
if (alreadyImportedLib != null) {
extManager.setExternalPath(externalLibName,
alreadyImportedLib.getPathname(), false);
@@ -1038,12 +977,12 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
* library names in the given list
*
* @param libraryNames A {@link List} of unprocessed library names
- * @param options The options
+ * @param settings The {@link Loader.ImporterSettings}
* @return A {@link Queue} of {@link UnprocessedLibrary}s
*/
private Queue createUnprocessedQueue(List libraryNames,
- List options) {
- int depth = getLibraryLoadDepth(options);
+ ImporterSettings settings) {
+ int depth = getLibraryLoadDepth(settings);
return libraryNames.stream()
.map(name -> new UnprocessedLibrary(name, depth, false))
.collect(Collectors.toCollection(LinkedList::new));
@@ -1081,14 +1020,13 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
* Subclasses can override it as needed.
*
* @param fsrl The search path {@link FSRL}
- * @param loadSpec The {@link LoadSpec} to use during load.
- * @param monitor A cancelable task monitor
+ * @param settings The {@link Loader.ImporterSettings}
* @return True is the search path is valid; otherwise, false
* @throws CancelledException if the user cancelled the load
*/
- protected boolean isValidSearchPath(FSRL fsrl, LoadSpec loadSpec, TaskMonitor monitor)
+ protected boolean isValidSearchPath(FSRL fsrl, ImporterSettings settings)
throws CancelledException {
- monitor.checkCancelled();
+ settings.monitor().checkCancelled();
return true;
}
@@ -1097,17 +1035,16 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
* libraries
*
* @param program The {@link Program} being loaded
- * @param loadSpec The {@link LoadSpec} to use during load
- * @param options The options
- * @param log The log
- * @param monitor A cancelable task monitor
+ * @param settings The {@link Loader.ImporterSettings}
* @return A {@link List} of priority-ordered {@link LibrarySearchPath}s used to search for
* libraries
* @throws CancelledException if the user cancelled the load
*/
- protected List getLibrarySearchPaths(Program program, LoadSpec loadSpec,
- List options, MessageLog log, TaskMonitor monitor) throws CancelledException {
- if (!isLoadLibraries(options) && !shouldSearchAllPaths(program, options, log)) {
+ protected List getLibrarySearchPaths(Program program,
+ ImporterSettings settings) throws CancelledException {
+
+ if (!isLoadLibraries(settings) && !shouldSearchAllPaths(program, settings) &&
+ !(isMirroredLayout(settings) && isLinkExistingLibraries(settings))) {
return List.of();
}
@@ -1115,9 +1052,10 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
List result = new ArrayList<>();
boolean success = false;
try {
- for (FSRL fsrl : LibrarySearchPathManager.getLibraryFsrlList(program, log, monitor)) {
+ for (FSRL fsrl : LibrarySearchPathManager.getLibraryFsrlList(program, settings.log(),
+ settings.monitor())) {
- if (!isValidSearchPath(fsrl, loadSpec, monitor)) {
+ if (!isValidSearchPath(fsrl, settings)) {
continue;
}
@@ -1126,7 +1064,7 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
// It might be a container file that we want to look inside of, so probe
if (fsService.getLocalFS().getLocalFile(fsrl).isFile()) {
FileSystemRef fileRef =
- fsService.probeFileForFilesystem(fsrl, monitor, null);
+ fsService.probeFileForFilesystem(fsrl, settings.monitor(), null);
if (fileRef != null) {
result.add(new LibrarySearchPath(fileRef, null));
continue;
@@ -1134,18 +1072,18 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
}
}
catch (IOException e) {
- log.appendMsg(e.getMessage());
+ settings.log().appendMsg(e.getMessage());
}
}
- try (RefdFile fileRef = fsService.getRefdFile(fsrl, monitor)) {
+ try (RefdFile fileRef = fsService.getRefdFile(fsrl, settings.monitor())) {
if (fileRef != null) {
result.add(
new LibrarySearchPath(fileRef.fsRef.dup(), fileRef.file.getPath()));
}
}
catch (IOException e) {
- log.appendMsg(e.getMessage());
+ settings.log().appendMsg(e.getMessage());
}
}
success = true;
@@ -1162,31 +1100,16 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
* Find the library within the given {@link List} of {@link Loaded} {@link Program}s
*
* @param loadedPrograms the list of {@link Loaded} {@link Program}s
- * @param libraryName The library name to lookup. Depending on the type of library, this could
+ * @param library The library name to lookup. Depending on the type of library, this could
* be a simple filename or an absolute path.
* @return The found {@link Loaded} {@link Program} or null if not found
*/
protected Loaded findLibraryInLoadedList(List> loadedPrograms,
- String libraryName) {
- Comparator comparator = getLibraryNameComparator();
- boolean noExtension = FilenameUtils.getExtension(libraryName).equals("");
- boolean absolute = libraryName.startsWith("/");
- for (Loaded loadedProgram : loadedPrograms) {
- String candidateName = loadedProgram.getName();
- if (isOptionalLibraryFilenameExtensions() && noExtension) {
- candidateName = FilenameUtils.getBaseName(candidateName);
- }
- if (absolute) {
- String loadedProgramPath = loadedProgram.getProjectFolderPath() + candidateName;
- if (loadedProgramPath.endsWith(libraryName)) {
- return loadedProgram;
- }
- }
- else if (comparator.compare(candidateName, libraryName) == 0) {
- return loadedProgram;
- }
- }
- return null;
+ String library) {
+ return loadedPrograms.stream()
+ .filter(e -> getLibraryNameComparator().compare(e.getName(), library) == 0)
+ .findFirst()
+ .orElse(null);
}
/**
@@ -1212,37 +1135,28 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
}
/**
- * Resolves the given library path to an existing {@link FSRL}. Some {@link Loader}s
- * have relaxed requirements on what counts as a valid library filename match. For example,
- * case-insensitive lookup may be allowed, and filename extensions may be optional.
+ * Looks in the given {@link GFileSystem} for the given library using the loader's
+ * {@link #getLibraryNameComparator() library name comparator}
*
- * @param fs The {@link GFileSystem file system} to resolve in
- * @param library The library. This will be either an absolute path, a relative path, or just a
- * filename.
- * @return The library resolved to an existing {@link FSRL}, or null if it did not resolve
+ * @param fs The {@link GFileSystem file system} to look in in
+ * @param library The library. Depending on the type of library, this could be a simple filename
+ * or an absolute path.
+ * @return A matching library {@link GFile}, or {@code null} if one was not found
* @throws IOException If an IO-related problem occurred
*/
- protected FSRL resolveLibraryFile(GFileSystem fs, String library) throws IOException {
- Comparator baseNameComp = getLibraryNameComparator();
- Comparator nameComp = isOptionalLibraryFilenameExtensions() &&
- FilenameUtils.getExtension(library).isEmpty()
- ? (s1, s2) -> baseNameComp.compare(FilenameUtils.getBaseName(s1),
- FilenameUtils.getBaseName(s2))
- : baseNameComp;
-
- GFile foundFile = fs.lookup(library, nameComp);
- return foundFile != null && !foundFile.isDirectory() ? foundFile.getFSRL() : null;
+ protected GFile lookupLibraryInFs(String library, GFileSystem fs) throws IOException {
+ GFile foundFile = fs.lookup(library, getLibraryNameComparator());
+ return foundFile != null && !foundFile.isDirectory() ? foundFile : null;
}
/**
- * Gets a {@link Comparator} for comparing library filenames
- *
- * @return A {@link Comparator} for comparing library filenames
+ * {@return a {@link Comparator} for comparing library names}
+ *
+ * No assumptions should be made about whether the library name includes path information or
+ * not.
*/
- private Comparator getLibraryNameComparator() {
- return isCaseInsensitiveLibraryFilenames()
- ? String.CASE_INSENSITIVE_ORDER
- : (s1, s2) -> s1.compareTo(s2);
+ protected Comparator getLibraryNameComparator() {
+ return (s1, s2) -> FilenameUtils.getName(s1).compareTo(FilenameUtils.getName(s2));
}
/**
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 cef9fd82e6..5e950ebf8c 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
@@ -27,7 +27,6 @@ import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.importer.MessageLog;
import ghidra.formats.gfilesystem.*;
import ghidra.framework.model.DomainObject;
-import ghidra.framework.model.Project;
import ghidra.framework.options.Options;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Program;
@@ -46,9 +45,9 @@ public abstract class AbstractOrdinalSupportLoader extends AbstractLibrarySuppor
@Override
public List getDefaultOptions(ByteProvider provider, LoadSpec loadSpec,
- DomainObject domainObject, boolean loadIntoProgram) {
- List list =
- super.getDefaultOptions(provider, loadSpec, domainObject, loadIntoProgram);
+ DomainObject domainObject, boolean loadIntoProgram, boolean mirrorFsLayout) {
+ List list = super.getDefaultOptions(provider, loadSpec, domainObject,
+ loadIntoProgram, mirrorFsLayout);
list.add(new Option(ORDINAL_LOOKUP_OPTION_NAME, ORDINAL_LOOKUP_OPTION_DEFAULT,
Boolean.class, Loader.COMMAND_LINE_ARG_PREFIX + "-ordinalLookup"));
return list;
@@ -71,26 +70,27 @@ public abstract class AbstractOrdinalSupportLoader extends AbstractLibrarySuppor
}
@Override
- protected boolean shouldSearchAllPaths(Program program, List options, MessageLog log) {
- return shouldPerformOrdinalLookup(options);
+ protected boolean shouldSearchAllPaths(Program program, ImporterSettings settings) {
+ return shouldPerformOrdinalLookup(settings);
}
@Override
- protected void processLibrary(Program lib, String libName, FSRL libFsrl, ByteProvider provider,
- Queue unprocessed, int depth, LoadSpec loadSpec,
- List options, MessageLog log, TaskMonitor monitor)
+ protected void processLibrary(Program lib, String libName, FSRL libFsrl,
+ Queue unprocessed, int depth, ImporterSettings settings)
throws IOException, CancelledException {
- int size = loadSpec.getLanguageCompilerSpec().getLanguageDescription().getSize();
+ MessageLog log = settings.log();
+ int size = settings.loadSpec().getLanguageCompilerSpec().getLanguageDescription().getSize();
ResourceFile existingExportsFile = LibraryLookupTable.getExistingExportsFile(libName, size);
- if (!shouldPerformOrdinalLookup(options)) {
+ if (!shouldPerformOrdinalLookup(settings)) {
return;
}
// Create exports file if necessary
if (existingExportsFile == null) {
try {
- ResourceFile newExportsFile = LibraryLookupTable.createFile(lib, true, monitor);
+ ResourceFile newExportsFile =
+ LibraryLookupTable.createFile(lib, true, settings.monitor());
log.appendMsg("Created exports file: " + newExportsFile);
}
catch (IOException e) {
@@ -126,23 +126,22 @@ public abstract class AbstractOrdinalSupportLoader extends AbstractLibrarySuppor
}
@Override
- protected void postLoadProgramFixups(List> loadedPrograms, Project project,
- LoadSpec loadSpec, List options, MessageLog messageLog, TaskMonitor monitor)
- throws CancelledException, IOException {
+ protected void postLoadProgramFixups(List> loadedPrograms,
+ ImporterSettings settings) throws CancelledException, IOException {
- if (shouldPerformOrdinalLookup(options)) {
+ if (shouldPerformOrdinalLookup(settings)) {
List> saveablePrograms = loadedPrograms
.stream()
.filter(loaded -> loaded.check(Predicate.not(Program::isTemporary)))
.toList();
- monitor.initialize(saveablePrograms.size());
+ settings.monitor().initialize(saveablePrograms.size());
for (Loaded loadedProgram : saveablePrograms) {
- monitor.checkCancelled();
+ settings.monitor().checkCancelled();
Program program = loadedProgram.getDomainObject(this);
int id = program.startTransaction("Ordinal fixups");
try {
- applyLibrarySymbols(program, messageLog, monitor);
- applyImports(program, messageLog, monitor);
+ applyLibrarySymbols(program, settings.log(), settings.monitor());
+ applyImports(program, settings.log(), settings.monitor());
}
finally {
program.endTransaction(id, true); // More efficient to commit when program will be discarded
@@ -151,8 +150,7 @@ public abstract class AbstractOrdinalSupportLoader extends AbstractLibrarySuppor
}
}
- super.postLoadProgramFixups(loadedPrograms, project, loadSpec, options, messageLog,
- monitor);
+ super.postLoadProgramFixups(loadedPrograms, settings);
}
@Override
@@ -164,11 +162,11 @@ public abstract class AbstractOrdinalSupportLoader extends AbstractLibrarySuppor
/**
* Checks to see if ordinal lookup should be performed
*
- * @param options a {@link List} of {@link Option}s
+ * @param settings The {@link Loader.ImporterSettings}
* @return True if ordinal lookup should be performed; otherwise, false
*/
- private boolean shouldPerformOrdinalLookup(List options) {
- return OptionUtils.getOption(ORDINAL_LOOKUP_OPTION_NAME, options,
+ private boolean shouldPerformOrdinalLookup(ImporterSettings settings) {
+ return OptionUtils.getOption(ORDINAL_LOOKUP_OPTION_NAME, settings.options(),
ORDINAL_LOOKUP_OPTION_DEFAULT);
}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/AbstractPeDebugLoader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/AbstractPeDebugLoader.java
index 56459113cf..62a218195f 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/AbstractPeDebugLoader.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/AbstractPeDebugLoader.java
@@ -51,9 +51,9 @@ abstract class AbstractPeDebugLoader extends AbstractOrdinalSupportLoader {
@Override
public List getDefaultOptions(ByteProvider provider, LoadSpec loadSpec,
- DomainObject domainObject, boolean loadIntoProgram) {
- List list =
- super.getDefaultOptions(provider, loadSpec, domainObject, loadIntoProgram);
+ DomainObject domainObject, boolean loadIntoProgram, boolean mirrorFsLayout) {
+ List list = super.getDefaultOptions(provider, loadSpec, domainObject,
+ loadIntoProgram, mirrorFsLayout);
list.add(new Option(SHOW_LINE_NUMBERS_OPTION_NAME, SHOW_LINE_NUMBERS_OPTION_DEFAULT,
Boolean.class, Loader.COMMAND_LINE_ARG_PREFIX + "-showDebugLineNumbers"));
return list;
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 229b7c99ea..6417e554e9 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
@@ -19,16 +19,13 @@ import java.io.IOException;
import java.io.InputStream;
import java.util.*;
-import org.apache.commons.io.FilenameUtils;
-
import ghidra.app.plugin.processors.generic.MemoryBlockDefinition;
import ghidra.app.util.Option;
import ghidra.app.util.OptionUtils;
import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.importer.MessageLog;
import ghidra.formats.gfilesystem.FSRL;
-import ghidra.formats.gfilesystem.FSUtilities;
-import ghidra.framework.model.*;
+import ghidra.framework.model.DomainObject;
import ghidra.framework.store.LockException;
import ghidra.program.database.ProgramDB;
import ghidra.program.database.function.OverlappingFunctionException;
@@ -67,33 +64,14 @@ public abstract class AbstractProgramLoader implements Loader {
* It is also the responsibility of the caller to close the returned {@link Loaded}
* {@link Program}s with {@link Loaded#close()} when they are no longer needed.
*
- * @param provider The bytes to load.
- * @param loadedName A suggested name for the primary {@link Loaded} {@link Program}.
- * This is just a suggestion, and a {@link Loader} implementation reserves the right to change
- * it. The {@link Loaded} {@link Program}s should be queried for their true names using
- * {@link Loaded#getName()}.
- * @param project The {@link Project}. Loaders can use this to take advantage of existing
- * {@link DomainFolder}s and {@link DomainFile}s to do custom behaviors such as loading
- * libraries. Could be null if there is no project.
- * @param projectFolderPath A suggested project folder path for the {@link Loaded}
- * {@link Program}s. This is just a suggestion, and a {@link Loader} implementation
- * reserves the right to change it for each {@link Loaded} result. The {@link Loaded}
- * {@link Program}s should be queried for their true project folder paths using
- * {@link Loaded#getProjectFolderPath()}.
- * @param loadSpec The {@link LoadSpec} to use during load.
- * @param options The load options.
- * @param log The message log.
- * @param consumer A consumer object for generated {@link Program}s.
- * @param monitor A task monitor.
+ * @param settings The {@link Loader.ImporterSettings}.
* @return A {@link List} of one or more {@link Loaded} {@link Program}s (created but not
* saved).
* @throws LoadException if the load failed in an expected way.
* @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 loadedName,
- Project project, String projectFolderPath, LoadSpec loadSpec, List options,
- MessageLog log, Object consumer, TaskMonitor monitor)
+ protected abstract List> loadProgram(ImporterSettings settings)
throws IOException, LoadException, CancelledException;
/**
@@ -102,40 +80,32 @@ public abstract class AbstractProgramLoader implements Loader {
*
* NOTE: The loading that occurs in this method will automatically be done in a transaction.
*
- * @param provider The bytes to load into the {@link Program}.
- * @param loadSpec The {@link LoadSpec} to use during load.
- * @param options The load options.
- * @param messageLog The message log.
* @param program The {@link Program} to load into.
- * @param monitor A cancelable task monitor.
+ * @param settings The {@link Loader.ImporterSettings}.
* @throws LoadException if the load failed in an expected way.
* @throws IOException if there was an IO-related problem loading.
* @throws CancelledException if the user cancelled the load.
*/
- protected abstract void loadProgramInto(ByteProvider provider, LoadSpec loadSpec,
- List options, MessageLog messageLog, Program program, TaskMonitor monitor)
+ protected abstract void loadProgramInto(Program program, ImporterSettings settings)
throws IOException, LoadException, CancelledException;
@Override
- public final LoadResults extends DomainObject> load(ByteProvider provider, String loadedName,
- Project project, String projectFolderPath, LoadSpec loadSpec, List options,
- MessageLog messageLog, Object consumer, TaskMonitor monitor) throws IOException,
- CancelledException, VersionException, LoadException {
+ public final LoadResults extends DomainObject> load(ImporterSettings settings)
+ throws IOException, CancelledException, VersionException, LoadException {
- if (!loadSpec.isComplete()) {
+ if (!settings.loadSpec().isComplete()) {
throw new LoadException("Load spec is incomplete");
}
- List> loadedPrograms = loadProgram(provider, loadedName, project,
- projectFolderPath, loadSpec, options, messageLog, consumer, monitor);
+ List> loadedPrograms = loadProgram(settings);
boolean success = false;
try {
for (Loaded loadedProgram : loadedPrograms) {
- monitor.checkCancelled();
+ settings.monitor().checkCancelled();
Program program = loadedProgram.getDomainObject(this);
try {
- applyProcessorLabels(options, program);
+ applyProcessorLabels(settings.options(), program);
program.setEventsEnabled(true);
}
finally {
@@ -144,7 +114,7 @@ public abstract class AbstractProgramLoader implements Loader {
}
// Subclasses can perform custom post-load fix-ups
- postLoadProgramFixups(loadedPrograms, project, loadSpec, options, messageLog, monitor);
+ postLoadProgramFixups(loadedPrograms, settings);
// Discard temporary programs
Iterator> iter = loadedPrograms.iterator();
@@ -168,11 +138,10 @@ public abstract class AbstractProgramLoader implements Loader {
}
@Override
- public final void loadInto(ByteProvider provider, LoadSpec loadSpec, List options,
- MessageLog messageLog, Program program, TaskMonitor monitor)
+ public final void loadInto(Program program, ImporterSettings settings)
throws IOException, LoadException, CancelledException {
- if (!loadSpec.isComplete()) {
+ if (!settings.loadSpec().isComplete()) {
throw new LoadException("Load spec is incomplete");
}
@@ -180,7 +149,7 @@ public abstract class AbstractProgramLoader implements Loader {
int transactionID = program.startTransaction("Loading - " + getName());
boolean success = false;
try {
- loadProgramInto(provider, loadSpec, options, messageLog, program, monitor);
+ loadProgramInto(program, settings);
success = true;
}
finally {
@@ -191,7 +160,7 @@ public abstract class AbstractProgramLoader implements Loader {
@Override
public List getDefaultOptions(ByteProvider provider, LoadSpec loadSpec,
- DomainObject domainObject, boolean isLoadIntoProgram) {
+ DomainObject domainObject, boolean loadIntoProgram, boolean mirrorFsLayout) {
ArrayList list = new ArrayList<>();
list.add(new Option(APPLY_LABELS_OPTION_NAME, shouldApplyProcessorLabelsByDefault(),
Boolean.class, Loader.COMMAND_LINE_ARG_PREFIX + "-applyLabels"));
@@ -223,17 +192,12 @@ public abstract class AbstractProgramLoader implements Loader {
* It provides subclasses an opportunity to do follow-on actions to the load.
*
* @param loadedPrograms The {@link Loaded loaded programs} to be fixed up.
- * @param project The {@link Project} to load into. Could be null if there is no project.
- * @param loadSpec The {@link LoadSpec} to use during load.
- * @param options The load options.
- * @param messageLog The message log.
- * @param monitor A cancelable task monitor.
+ * @param settings The {@link Loader.ImporterSettings}.
* @throws IOException if there was an IO-related problem loading.
* @throws CancelledException if the user cancelled the load.
*/
- protected void postLoadProgramFixups(List> loadedPrograms, Project project,
- LoadSpec loadSpec, List options, MessageLog messageLog, TaskMonitor monitor)
- throws CancelledException, IOException {
+ protected void postLoadProgramFixups(List> loadedPrograms,
+ ImporterSettings settings) throws CancelledException, IOException {
// Default behavior is to do nothing
}
@@ -261,21 +225,6 @@ public abstract class AbstractProgramLoader implements Loader {
return false;
}
- /**
- * Joins the given path elements to form a single path. Empty and null path elements
- * are ignored. The returned path's separators are converted to unix-style and
- * windows-specific characters like {@code :} are stripped out, making the path suitable
- * to be a project path
- *
- * @param pathElements The path elements to append to one another
- * @return A single path consisting of the given path elements appended together
- * @see FSUtilities#appendPath(String...)
- */
- protected String joinPaths(String... pathElements) {
- String str = FSUtilities.appendPath(pathElements);
- return str != null ? FilenameUtils.separatorsToUnix(str).replaceAll(":", "") : null;
- }
-
/**
* Generates a block name.
*
@@ -302,28 +251,26 @@ public abstract class AbstractProgramLoader implements Loader {
/**
* Creates a {@link Program} with the specified attributes.
*
- * @param provider The bytes that will make up the {@link Program}.
- * @param domainFileName The name for the DomainFile that will store the {@link Program}.
* @param imageBase The image base address of the {@link Program}.
- * @param executableFormatName The file format name of the {@link Program}. Typically this will
- * be the {@link Loader} name.
- * @param language The {@link Language} of the {@link Program}.
- * @param compilerSpec The {@link CompilerSpec} of the {@link Program}.
- * @param consumer A consumer object for the {@link Program} generated.
+ * @param settings The {@link Loader.ImporterSettings}.
* @return The newly created {@link Program}.
* @throws IOException if there was an IO-related problem with creating the {@link Program}.
*/
- protected Program createProgram(ByteProvider provider, String domainFileName,
- Address imageBase, String executableFormatName, Language language,
- CompilerSpec compilerSpec, Object consumer) throws IOException {
+ protected Program createProgram(Address imageBase, ImporterSettings settings)
+ throws IOException {
- String programName = getProgramNameFromSourceData(provider, domainFileName);
- Program prog = new ProgramDB(programName, language, compilerSpec, consumer);
+ LanguageCompilerSpecPair pair = settings.loadSpec().getLanguageCompilerSpec();
+ Language language = getLanguageService().getLanguage(pair.languageID);
+ CompilerSpec compilerSpec = language.getCompilerSpecByID(pair.compilerSpecID);
+
+ String programName =
+ getProgramNameFromSourceData(settings.provider(), settings.importNameOnly());
+ Program prog = new ProgramDB(programName, language, compilerSpec, settings.consumer());
prog.setEventsEnabled(false);
int id = prog.startTransaction("Set program properties");
boolean success = false;
try {
- setProgramProperties(prog, provider, executableFormatName);
+ setProgramProperties(prog, settings.provider(), getName());
try {
if (shouldSetImageBase(prog, imageBase)) {
prog.setImageBase(imageBase, true);
@@ -339,11 +286,30 @@ public abstract class AbstractProgramLoader implements Loader {
finally {
prog.endTransaction(id, true); // More efficient to commit when program will be discarded
if (!success) {
- prog.release(consumer);
+ prog.release(settings.consumer());
}
}
}
+ /**
+ * Creates a {@link Program} with the specified attributes at the {@link LoadSpec}'s desired
+ * image base
+ *
+ * @param settings The {@link Loader.ImporterSettings}.
+ * @return The newly created {@link Program}.
+ * @throws IOException if there was an IO-related problem with creating the {@link Program}.
+ */
+ protected Program createProgram(ImporterSettings settings) throws IOException {
+
+ Address imageBaseAddr = getLanguageService()
+ .getLanguage(settings.loadSpec().getLanguageCompilerSpec().languageID)
+ .getAddressFactory()
+ .getDefaultAddressSpace()
+ .getAddress(settings.loadSpec().getDesiredImageBase());
+
+ return createProgram(imageBaseAddr, settings);
+ }
+
/**
* Sets a program's Executable Path, Executable Format, MD5, SHA256, and FSRL properties.
*
@@ -387,13 +353,14 @@ public abstract class AbstractProgramLoader implements Loader {
* Creates default memory blocks for the given {@link Program}.
*
* @param program The {@link Program} to create default memory blocks for.
- * @param language The {@link Program}s {@link Language}.
- * @param log The log to use during memory block creation.
+ * @param settings The {@link Loader.ImporterSettings}.
*/
- protected void createDefaultMemoryBlocks(Program program, Language language, MessageLog log) {
+ protected void createDefaultMemoryBlocks(Program program, ImporterSettings settings) {
+ MessageLog log = settings.log();
int id = program.startTransaction("Create default blocks");
try {
-
+ LanguageCompilerSpecPair pair = settings.loadSpec().getLanguageCompilerSpec();
+ Language language = getLanguageService().getLanguage(pair.languageID);
MemoryBlockDefinition[] defaultMemoryBlocks = language.getDefaultMemoryBlocks();
if (defaultMemoryBlocks == null) {
return;
@@ -423,6 +390,10 @@ public abstract class AbstractProgramLoader implements Loader {
}
}
}
+ catch (LanguageNotFoundException e) {
+ log.appendMsg("Failed get language for: " +
+ settings.loadSpec().getLanguageCompilerSpec().languageID);
+ }
finally {
program.endTransaction(id, true);
}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/AbstractProgramWrapperLoader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/AbstractProgramWrapperLoader.java
index d174dadebf..7ce69dc12e 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/AbstractProgramWrapperLoader.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/AbstractProgramWrapperLoader.java
@@ -18,15 +18,9 @@ package ghidra.app.util.opinion;
import java.io.IOException;
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.Project;
-import ghidra.program.model.address.Address;
import ghidra.program.model.lang.*;
import ghidra.program.model.listing.Program;
import ghidra.util.exception.CancelledException;
-import ghidra.util.task.TaskMonitor;
/**
* An abstract {@link Loader} that provides a convenience wrapper around
@@ -38,69 +32,51 @@ public abstract class AbstractProgramWrapperLoader extends AbstractProgramLoader
/**
* Loads bytes in a particular format into the given {@link Program}.
*
- * @param provider The bytes to load.
- * @param loadSpec The {@link LoadSpec} to use during load.
- * @param options The load options.
* @param program The {@link Program} to load into.
- * @param monitor A cancelable task monitor.
- * @param log The message log.
+ * @param settings The {@link Loader.ImporterSettings}.
* @throws IOException if there was an IO-related problem loading.
* @throws CancelledException if the user cancelled the load.
*/
- protected abstract void load(ByteProvider provider, LoadSpec loadSpec, List options,
- Program program, TaskMonitor monitor, MessageLog log)
+ protected abstract void load(Program program, ImporterSettings settings)
throws CancelledException, IOException;
@Override
- protected List> loadProgram(ByteProvider provider, String programName,
- Project project, String programFolderPath, LoadSpec loadSpec, List options,
- MessageLog log, Object consumer, TaskMonitor monitor)
+ protected List> loadProgram(ImporterSettings settings)
throws IOException, CancelledException {
- LanguageCompilerSpecPair pair = loadSpec.getLanguageCompilerSpec();
- Language language = getLanguageService().getLanguage(pair.languageID);
- CompilerSpec compilerSpec = language.getCompilerSpecByID(pair.compilerSpecID);
-
- Address imageBaseAddr = language.getAddressFactory()
- .getDefaultAddressSpace()
- .getAddress(loadSpec.getDesiredImageBase());
-
- Program program = createProgram(provider, programName, imageBaseAddr, getName(), language,
- compilerSpec, consumer);
- List> loadedList = List.of(
- new Loaded(program, programName, project, programFolderPath, consumer));
+ Program program = createProgram(settings);
+ Loaded loaded = new Loaded(program, settings);
int transactionID = program.startTransaction("Loading");
boolean success = false;
try {
- load(provider, loadSpec, options, program, monitor, log);
- createDefaultMemoryBlocks(program, language, log);
+ load(program, settings);
+ createDefaultMemoryBlocks(program, settings);
success = true;
- return loadedList;
+ return List.of(loaded);
}
finally {
program.endTransaction(transactionID, true); // More efficient to commit when program will be discarded
if (!success) {
- loadedList.forEach(Loaded::close);
+ loaded.close();
}
}
}
@Override
- protected void loadProgramInto(ByteProvider provider, LoadSpec loadSpec,
- List options, MessageLog log, Program program, TaskMonitor monitor)
+ protected void loadProgramInto(Program program, ImporterSettings settings)
throws CancelledException, LoadException, IOException {
- LanguageCompilerSpecPair pair = loadSpec.getLanguageCompilerSpec();
+ LanguageCompilerSpecPair pair = settings.loadSpec().getLanguageCompilerSpec();
LanguageID languageID = program.getLanguageID();
CompilerSpecID compilerSpecID = program.getCompilerSpec().getCompilerSpecID();
if (!(pair.languageID.equals(languageID) && pair.compilerSpecID.equals(compilerSpecID))) {
- String message = provider.getAbsolutePath() +
+ String message = settings.provider().getAbsolutePath() +
" does not have the same language/compiler spec as program " + program.getName();
- log.appendMsg(message);
+ settings.log().appendMsg(message);
throw new LoadException(message);
}
- load(provider, loadSpec, options, program, monitor, log);
+ load(program, settings);
}
@Override
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 4c3aa96736..8b01e66fe0 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
@@ -22,7 +22,6 @@ import ghidra.app.util.*;
import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.importer.MessageLog;
import ghidra.framework.model.DomainObject;
-import ghidra.framework.model.Project;
import ghidra.program.database.mem.FileBytes;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.*;
@@ -31,7 +30,6 @@ import ghidra.program.model.mem.Memory;
import ghidra.util.Msg;
import ghidra.util.NumericUtilities;
import ghidra.util.exception.CancelledException;
-import ghidra.util.task.TaskMonitor;
public class BinaryLoader extends AbstractProgramLoader {
@@ -269,55 +267,48 @@ public class BinaryLoader extends AbstractProgramLoader {
}
@Override
- protected List> loadProgram(ByteProvider provider, String programName,
- Project project, String programFolderPath, LoadSpec loadSpec, List options,
- MessageLog log, Object consumer, TaskMonitor monitor)
+ protected List> loadProgram(ImporterSettings settings)
throws IOException, CancelledException {
- LanguageCompilerSpecPair pair = loadSpec.getLanguageCompilerSpec();
+ LanguageCompilerSpecPair pair = settings.loadSpec().getLanguageCompilerSpec();
Language importerLanguage = getLanguageService().getLanguage(pair.languageID);
- CompilerSpec importerCompilerSpec =
- importerLanguage.getCompilerSpecByID(pair.compilerSpecID);
-
Address baseAddr =
importerLanguage.getAddressFactory().getDefaultAddressSpace().getAddress(0);
- Program prog = createProgram(provider, programName, baseAddr, getName(), importerLanguage,
- importerCompilerSpec, consumer);
- List> loadedList =
- List.of(new Loaded<>(prog, programName, project, programFolderPath, consumer));
+
+ Program prog = createProgram(baseAddr, settings);
+ Loaded loaded = new Loaded(prog, settings);
boolean success = false;
try {
- loadInto(provider, loadSpec, options, log, prog, monitor);
- createDefaultMemoryBlocks(prog, importerLanguage, log);
+ loadInto(prog, settings);
+ createDefaultMemoryBlocks(prog, settings);
success = true;
- return loadedList;
+ return List.of(loaded);
}
finally {
if (!success) {
- loadedList.forEach(Loaded::close);
+ loaded.close();
}
}
}
@Override
- protected void loadProgramInto(ByteProvider provider, LoadSpec loadSpec,
- List options, MessageLog log, Program prog, TaskMonitor monitor)
+ protected void loadProgramInto(Program prog, ImporterSettings settings)
throws IOException, LoadException, CancelledException {
- long length = getLength(options);
+ long length = getLength(settings.options());
//File file = provider.getFile();
- long fileOffset = getFileOffset(options);
- Address baseAddr = getBaseAddr(options);
- String blockName = getBlockName(options);
- boolean isOverlay = isOverlay(options);
+ long fileOffset = getFileOffset(settings.options());
+ Address baseAddr = getBaseAddr(settings.options());
+ String blockName = getBlockName(settings.options());
+ boolean isOverlay = isOverlay(settings.options());
if (length == 0) {
- length = provider.length();
+ length = settings.provider().length();
}
- length = clipToMemorySpace(length, log, prog);
+ length = clipToMemorySpace(length, settings.log(), prog);
- FileBytes fileBytes =
- MemoryBlockUtils.createFileBytes(prog, provider, fileOffset, length, monitor);
+ FileBytes fileBytes = MemoryBlockUtils.createFileBytes(prog, settings.provider(),
+ fileOffset, length, settings.monitor());
try {
AddressSpace space = prog.getAddressFactory().getDefaultAddressSpace();
if (baseAddr == null) {
@@ -326,7 +317,7 @@ public class BinaryLoader extends AbstractProgramLoader {
if (blockName == null || blockName.length() == 0) {
blockName = generateBlockName(prog, isOverlay, baseAddr.getAddressSpace());
}
- createBlock(prog, isOverlay, blockName, baseAddr, fileBytes, length, log);
+ createBlock(prog, isOverlay, blockName, baseAddr, fileBytes, length, settings.log());
}
catch (AddressOverflowException e) {
throw new LoadException("Invalid address range specified: start:" + baseAddr +
@@ -359,7 +350,7 @@ public class BinaryLoader extends AbstractProgramLoader {
@Override
public List getDefaultOptions(ByteProvider provider, LoadSpec loadSpec,
- DomainObject domainObject, boolean loadIntoProgram) {
+ DomainObject domainObject, boolean loadIntoProgram, boolean mirrorFsLayout) {
long fileOffset = 0;
long origFileLength = -1;
try {
@@ -404,7 +395,8 @@ public class BinaryLoader extends AbstractProgramLoader {
list.add(new Option(OPTION_NAME_LEN, new HexLong(length), HexLong.class,
Loader.COMMAND_LINE_ARG_PREFIX + "-length"));
- list.addAll(super.getDefaultOptions(provider, loadSpec, domainObject, loadIntoProgram));
+ list.addAll(super.getDefaultOptions(provider, loadSpec, domainObject, loadIntoProgram,
+ mirrorFsLayout));
return list;
}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/CoffLoader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/CoffLoader.java
index 05309622fc..cd413b9ad1 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/CoffLoader.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/CoffLoader.java
@@ -123,9 +123,9 @@ public class CoffLoader extends AbstractLibrarySupportLoader {
@Override
public List getDefaultOptions(ByteProvider provider, LoadSpec loadSpec,
- DomainObject domainObject, boolean loadIntoProgram) {
- List list =
- super.getDefaultOptions(provider, loadSpec, domainObject, loadIntoProgram);
+ DomainObject domainObject, boolean loadIntoProgram, boolean mirrorFsLayout) {
+ List list = super.getDefaultOptions(provider, loadSpec, domainObject,
+ loadIntoProgram, mirrorFsLayout);
if (!loadIntoProgram) {
list.add(new Option(FAKE_LINK_OPTION_NAME, FAKE_LINK_OPTION_DEFAULT));
}
@@ -162,21 +162,23 @@ public class CoffLoader extends AbstractLibrarySupportLoader {
}
@Override
- protected void load(ByteProvider provider, LoadSpec loadSpec, List options,
- Program program, TaskMonitor monitor, MessageLog log)
+ protected void load(Program program, ImporterSettings settings)
throws IOException, CancelledException {
+ MessageLog log = settings.log();
+ TaskMonitor monitor = settings.monitor();
try {
- CoffFileHeader header = new CoffFileHeader(provider);
+ CoffFileHeader header = new CoffFileHeader(settings.provider());
header.parse(monitor);
Map sectionsMap = new HashMap<>();
Map symbolsMap = new HashMap<>();
- FileBytes fileBytes = MemoryBlockUtils.createFileBytes(program, provider, monitor);
- processSectionHeaders(provider, header, program, fileBytes, monitor, log, sectionsMap,
- performFakeLinking(options));
+ FileBytes fileBytes =
+ MemoryBlockUtils.createFileBytes(program, settings.provider(), monitor);
+ processSectionHeaders(settings.provider(), header, program, fileBytes, monitor, log,
+ sectionsMap, performFakeLinking(settings.options()));
processSymbols(header, program, monitor, log, sectionsMap, symbolsMap);
processEntryPoint(header, program, monitor, log);
processRelocations(header, program, sectionsMap, symbolsMap, log, monitor);
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/DbgLoader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/DbgLoader.java
index a65fc3166b..81202e9b61 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/DbgLoader.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/DbgLoader.java
@@ -19,15 +19,12 @@ import java.io.File;
import java.io.IOException;
import java.util.*;
-import ghidra.app.util.Option;
import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.bin.RandomAccessByteProvider;
import ghidra.app.util.bin.format.pe.*;
import ghidra.app.util.bin.format.pe.PortableExecutable.SectionLayout;
-import ghidra.app.util.importer.MessageLog;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program;
-import ghidra.util.task.TaskMonitor;
/**
* An opinion service for processing Microsoft DBG files.
@@ -68,15 +65,14 @@ public class DbgLoader extends AbstractPeDebugLoader {
}
@Override
- public void load(ByteProvider provider, LoadSpec loadSpec, List options, Program prog,
- TaskMonitor monitor, MessageLog log) throws IOException {
+ public void load(Program prog, ImporterSettings settings) throws IOException {
if (!prog.getExecutableFormat().equals(PeLoader.PE_NAME)) {
throw new IOException("Loading of DBG file may only be 'added' to existing " +
PeLoader.PE_NAME + " Program");
}
- SeparateDebugHeader debug = new SeparateDebugHeader(provider);
+ SeparateDebugHeader debug = new SeparateDebugHeader(settings.provider());
String parentPath = prog.getExecutablePath();
File parentFile = new File(parentPath);
@@ -93,8 +89,8 @@ public class DbgLoader extends AbstractPeDebugLoader {
sectionToAddress.put(sectionHeader,
imageBase.add(sectionHeader.getVirtualAddress()));
}
- processDebug(debug.getParser(), parentPE.getNTHeader(), sectionToAddress, prog, options,
- monitor);
+ processDebug(debug.getParser(), parentPE.getNTHeader(), sectionToAddress, prog,
+ settings.options(), settings.monitor());
}
finally {
if (provider2 != null) {
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/DecompileDebugXmlLoader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/DecompileDebugXmlLoader.java
index 7138537687..59e66be93a 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/DecompileDebugXmlLoader.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/DecompileDebugXmlLoader.java
@@ -24,7 +24,6 @@ import ghidra.app.plugin.core.analysis.AutoAnalysisManager;
import ghidra.app.util.Option;
import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.importer.MessageLog;
-import ghidra.framework.model.Project;
import ghidra.program.model.address.Address;
import ghidra.program.model.lang.*;
import ghidra.program.model.listing.Program;
@@ -101,31 +100,16 @@ public class DecompileDebugXmlLoader extends AbstractProgramLoader {
/**
* After initial parsing of the XML file, load the details into a new program for
* loading/viewing in Ghidra.
- *
- * @param provider ByteProvider
- * @param programName Program name from the XML
- * @param project active project
- * @param programFolderPath folder path to XML
- * @param loadSpec String parsed out of the XML for details regarding language/compiler spec
- * @param options program options - will always be empty
- * @param log MessageLog
- * @param consumer Object
- * @param monitor TaskMonitor
- *
- * @return List of loaded programs - should always be 1
+ *
+ * {@inheritDoc}
*/
@Override
- protected List> loadProgram(ByteProvider provider, String programName,
- Project project,
- String programFolderPath, LoadSpec loadSpec, List options, MessageLog log,
- Object consumer,
- TaskMonitor monitor) throws IOException, CancelledException {
+ protected List> loadProgram(ImporterSettings settings)
+ throws IOException, CancelledException {
- LanguageCompilerSpecPair pair = loadSpec.getLanguageCompilerSpec();
+ LanguageCompilerSpecPair pair = settings.loadSpec().getLanguageCompilerSpec();
Language importerLanguage = getLanguageService().getLanguage(pair.languageID);
- CompilerSpec importerCompilerSpec =
- importerLanguage.getCompilerSpecByID(pair.compilerSpecID);
- ParseGhidraDebugResult parsedResult = parse(provider);
+ ParseGhidraDebugResult parsedResult = parse(settings.provider());
if (parsedResult.lastInfo == null) {
return new ArrayList>();
}
@@ -136,15 +120,14 @@ public class DecompileDebugXmlLoader extends AbstractProgramLoader {
importerLanguage.getAddressFactory().getAddress(parsedResult.lastInfo.offset());
}
- Program prog = createProgram(provider, programName, imageBase, getName(),
- importerLanguage, importerCompilerSpec, consumer);
- List> loadedList =
- List.of(new Loaded<>(prog, programName, project, programFolderPath, consumer));
+ Program prog = createProgram(imageBase, settings);
+ List> loadedList = List.of(new Loaded<>(prog, settings));
int loadingId = prog.startTransaction("Loading debug XML file");
try {
- doImport(parsedResult.debugXmlMgr, options, log, prog, monitor, false, programName);
+ doImport(parsedResult.debugXmlMgr, settings.options(), settings.log(), prog,
+ settings.monitor(), false, settings.importName());
}
catch (
@@ -304,8 +287,7 @@ public class DecompileDebugXmlLoader extends AbstractProgramLoader {
}
@Override
- protected void loadProgramInto(ByteProvider provider, LoadSpec loadSpec, List options,
- MessageLog messageLog, Program program, TaskMonitor monitor)
+ protected void loadProgramInto(Program program, ImporterSettings settings)
throws IOException, LoadException, CancelledException {
// since we will not ever be loading this debug program into an existing program, this
// should not be used.
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/DefLoader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/DefLoader.java
index 01dcf7e6e4..00ecb6bb9c 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/DefLoader.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/DefLoader.java
@@ -4,9 +4,9 @@
* 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.
@@ -19,13 +19,10 @@ import java.io.*;
import java.util.*;
import java.util.function.Consumer;
-import ghidra.app.util.Option;
import ghidra.app.util.bin.ByteProvider;
-import ghidra.app.util.importer.MessageLog;
import ghidra.program.model.listing.Program;
import ghidra.program.model.symbol.*;
import ghidra.util.exception.InvalidInputException;
-import ghidra.util.task.TaskMonitor;
/**
* A {@link Loader} for processing Microsoft DEF files.
@@ -80,16 +77,15 @@ public class DefLoader extends AbstractProgramWrapperLoader {
}
@Override
- public void load(ByteProvider provider, LoadSpec loadSpec, List options, Program prog,
- TaskMonitor monitor, MessageLog log) throws IOException {
+ public void load(Program prog, ImporterSettings settings) throws IOException {
if (!prog.getExecutableFormat().equals(PeLoader.PE_NAME)) {
throw new IOException("Program must be a " + PeLoader.PE_NAME);
}
SymbolTable symtab = prog.getSymbolTable();
- Consumer errorConsumer = err -> log.appendMsg("DefLoader", err);
- for (DefExportLine def : parseExports(provider)) {
+ Consumer errorConsumer = err -> settings.log().appendMsg("DefLoader", err);
+ for (DefExportLine def : parseExports(settings.provider())) {
Integer ordinal = def.getOrdinal();
if (ordinal == null) {
continue;
@@ -105,7 +101,7 @@ public class DefLoader extends AbstractProgramWrapperLoader {
label.setPrimary();
}
catch (InvalidInputException e) {
- log.appendMsg(e.getMessage());
+ settings.log().appendMsg(e.getMessage());
}
}
}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/DyldCacheLoader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/DyldCacheLoader.java
index f09f7ee20e..03cdb9b05a 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/DyldCacheLoader.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/DyldCacheLoader.java
@@ -4,9 +4,9 @@
* 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.
@@ -23,11 +23,9 @@ import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.bin.format.macho.dyld.DyldArchitecture;
import ghidra.app.util.bin.format.macho.dyld.DyldCacheHeader;
-import ghidra.app.util.importer.MessageLog;
import ghidra.framework.model.DomainObject;
import ghidra.program.model.listing.Program;
import ghidra.util.exception.CancelledException;
-import ghidra.util.task.TaskMonitor;
/**
* A {@link Loader} for DYLD shared cache files.
@@ -129,13 +127,12 @@ public class DyldCacheLoader extends AbstractProgramWrapperLoader {
}
@Override
- public void load(ByteProvider provider, LoadSpec loadSpec, List options,
- Program program, TaskMonitor monitor, MessageLog log) throws IOException {
+ public void load(Program program, ImporterSettings settings) throws IOException {
try {
- DyldCacheProgramBuilder.buildProgram(program, provider,
- MemoryBlockUtils.createFileBytes(program, provider, monitor),
- getDyldCacheOptions(options), log, monitor);
+ DyldCacheProgramBuilder.buildProgram(program, settings.provider(),
+ MemoryBlockUtils.createFileBytes(program, settings.provider(), settings.monitor()),
+ getDyldCacheOptions(settings.options()), settings.log(), settings.monitor());
}
catch (CancelledException e) {
return;
@@ -147,9 +144,9 @@ public class DyldCacheLoader extends AbstractProgramWrapperLoader {
@Override
public List getDefaultOptions(ByteProvider provider, LoadSpec loadSpec,
- DomainObject domainObject, boolean loadIntoProgram) {
- List list =
- super.getDefaultOptions(provider, loadSpec, domainObject, loadIntoProgram);
+ DomainObject domainObject, boolean loadIntoProgram, boolean mirrorFsLayout) {
+ List list = super.getDefaultOptions(provider, loadSpec, domainObject,
+ loadIntoProgram, mirrorFsLayout);
if (!loadIntoProgram) {
list.add(new Option(FIXUP_SLIDE_POINTERS_OPTION_NAME,
FIXUP_SLIDE_POINTERS_OPTION_DEFAULT, Boolean.class,
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 8ff7bada3c..2579889e11 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
@@ -22,8 +22,8 @@ import ghidra.app.util.Option;
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.*;
+import ghidra.framework.model.DomainObject;
+import ghidra.framework.model.ProjectData;
import ghidra.framework.options.Options;
import ghidra.program.model.lang.Endian;
import ghidra.program.model.listing.Program;
@@ -31,7 +31,6 @@ import ghidra.program.util.ExternalSymbolResolver;
import ghidra.util.Msg;
import ghidra.util.NumericUtilities;
import ghidra.util.exception.CancelledException;
-import ghidra.util.task.TaskMonitor;
/**
* A {@link Loader} for processing executable and linking files (ELF).
@@ -65,12 +64,12 @@ public class ElfLoader extends AbstractLibrarySupportLoader {
@Override
public List getDefaultOptions(ByteProvider provider, LoadSpec loadSpec,
- DomainObject domainObject, boolean loadIntoProgram) {
+ DomainObject domainObject, boolean loadIntoProgram, boolean mirrorFsLayout) {
// NOTE: add-to-program is not supported
- List options =
- super.getDefaultOptions(provider, loadSpec, domainObject, loadIntoProgram);
+ List options = super.getDefaultOptions(provider, loadSpec, domainObject,
+ loadIntoProgram, mirrorFsLayout);
try {
ElfLoaderOptionsFactory.addOptions(options, provider, loadSpec);
@@ -87,7 +86,7 @@ public class ElfLoader extends AbstractLibrarySupportLoader {
public String validateOptions(ByteProvider provider, LoadSpec loadSpec, List options,
Program program) {
if (options != null) {
- String validationErrorStr = ElfLoaderOptionsFactory.validateOptions(loadSpec, options);
+ String validationErrorStr =ElfLoaderOptionsFactory.validateOptions(loadSpec, options);
if (validationErrorStr != null) {
return validationErrorStr;
}
@@ -138,13 +137,14 @@ public class ElfLoader extends AbstractLibrarySupportLoader {
}
@Override
- public void load(ByteProvider provider, LoadSpec loadSpec, List options,
- Program program, TaskMonitor monitor, MessageLog log)
+ public void load(Program program, ImporterSettings settings)
throws IOException, CancelledException {
try {
- ElfHeader elf = new ElfHeader(provider, msg -> log.appendMsg(msg));
- ElfProgramBuilder.loadElf(elf, program, options, log, monitor);
+ ElfHeader elf =
+ new ElfHeader(settings.provider(), msg -> settings.log().appendMsg(msg));
+ ElfProgramBuilder.loadElf(elf, program, settings.options(), settings.log(),
+ settings.monitor());
}
catch (ElfException e) {
throw new IOException(e.getMessage());
@@ -152,17 +152,17 @@ public class ElfLoader extends AbstractLibrarySupportLoader {
}
@Override
- protected void postLoadProgramFixups(List> loadedPrograms, Project project,
- LoadSpec loadSpec, List options, MessageLog messageLog, TaskMonitor monitor)
- throws CancelledException, IOException {
- super.postLoadProgramFixups(loadedPrograms, project, loadSpec, options, messageLog,
- monitor);
+ protected void postLoadProgramFixups(List> loadedPrograms,
+ ImporterSettings settings) throws CancelledException, IOException {
+ super.postLoadProgramFixups(loadedPrograms, settings);
- ProjectData projectData = project != null ? project.getProjectData() : null;
- try (ExternalSymbolResolver esr = new ExternalSymbolResolver(projectData, monitor)) {
+ ProjectData projectData =
+ settings.project() != null ? settings.project().getProjectData() : null;
+ try (ExternalSymbolResolver esr =
+ new ExternalSymbolResolver(projectData, settings.monitor())) {
loadedPrograms.forEach(p -> esr.addProgramToFixup(p));
esr.fixUnresolvedExternalSymbols();
- esr.logInfo(messageLog::appendMsg, true);
+ esr.logInfo(settings.log()::appendMsg, true);
}
}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/GdtLoader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/GdtLoader.java
index fc1be174c7..89870d3174 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/GdtLoader.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/GdtLoader.java
@@ -23,11 +23,9 @@ import org.apache.commons.io.FilenameUtils;
import db.DBHandle;
import ghidra.app.util.Option;
import ghidra.app.util.bin.ByteProvider;
-import ghidra.app.util.importer.MessageLog;
import ghidra.framework.Application;
import ghidra.framework.data.OpenMode;
import ghidra.framework.model.DomainObject;
-import ghidra.framework.model.Project;
import ghidra.framework.store.db.PackedDatabase;
import ghidra.framework.store.local.ItemSerializer;
import ghidra.program.database.DataTypeArchiveContentHandler;
@@ -48,19 +46,17 @@ public class GdtLoader implements Loader {
@Override
public List getDefaultOptions(ByteProvider provider, LoadSpec loadSpec,
- DomainObject domainObject, boolean loadIntoProgram) {
+ DomainObject domainObject, boolean loadIntoProgram, boolean mirrorFsLayout) {
return Collections.emptyList();
}
@Override
- public LoadResults extends DomainObject> load(ByteProvider provider, String filename,
- Project project, String projectFolderPath, LoadSpec loadSpec, List options,
- MessageLog messageLog, Object consumer, TaskMonitor monitor)
+ public LoadResults extends DomainObject> load(ImporterSettings settings)
throws IOException, CancelledException, VersionException {
- DataTypeArchive dtArchive =
- loadPackedProgramDatabase(provider, filename, consumer, monitor);
- return new LoadResults<>(dtArchive, filename, project, projectFolderPath, consumer);
+ DataTypeArchive dtArchive = loadPackedProgramDatabase(settings.provider(),
+ settings.importName(), settings.consumer(), settings.monitor());
+ return new LoadResults<>(new Loaded<>(dtArchive, settings));
}
private DataTypeArchive loadPackedProgramDatabase(ByteProvider provider, String programName,
@@ -109,8 +105,7 @@ public class GdtLoader implements Loader {
}
@Override
- public void loadInto(ByteProvider provider, LoadSpec loadSpec, List options,
- MessageLog messageLog, Program program, TaskMonitor monitor)
+ public void loadInto(Program program, ImporterSettings settings)
throws IOException, LoadException, CancelledException {
throw new LoadException("Cannot add GDT to program");
}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/GzfLoader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/GzfLoader.java
index d1e02e3010..e60dd02034 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/GzfLoader.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/GzfLoader.java
@@ -23,11 +23,9 @@ import org.apache.commons.io.FilenameUtils;
import db.DBHandle;
import ghidra.app.util.Option;
import ghidra.app.util.bin.ByteProvider;
-import ghidra.app.util.importer.MessageLog;
import ghidra.framework.Application;
import ghidra.framework.data.OpenMode;
import ghidra.framework.model.DomainObject;
-import ghidra.framework.model.Project;
import ghidra.framework.store.db.PackedDatabase;
import ghidra.framework.store.local.ItemSerializer;
import ghidra.program.database.ProgramContentHandler;
@@ -67,18 +65,17 @@ public class GzfLoader implements Loader {
@Override
public List getDefaultOptions(ByteProvider provider, LoadSpec loadSpec,
- DomainObject domainObject, boolean loadIntoProgram) {
+ DomainObject domainObject, boolean loadIntoProgram, boolean mirrorFsLayout) {
return Collections.emptyList();
}
@Override
- public LoadResults extends DomainObject> load(ByteProvider provider, String programName,
- Project project, String projectFolderPath, LoadSpec loadSpec, List options,
- MessageLog messageLog, Object consumer, TaskMonitor monitor)
+ public LoadResults extends DomainObject> load(ImporterSettings settings)
throws IOException, CancelledException, VersionException {
- Program program = loadPackedProgramDatabase(provider, programName, consumer, monitor);
- return new LoadResults<>(program, programName, project, projectFolderPath, consumer);
+ Program program = loadPackedProgramDatabase(settings.provider(), settings.importName(),
+ settings.consumer(), settings.monitor());
+ return new LoadResults<>(new Loaded<>(program, settings));
}
private Program loadPackedProgramDatabase(ByteProvider provider, String programName,
@@ -127,8 +124,7 @@ public class GzfLoader implements Loader {
}
@Override
- public void loadInto(ByteProvider provider, LoadSpec loadSpec, List options,
- MessageLog messageLog, Program program, TaskMonitor monitor)
+ public void loadInto(Program program, ImporterSettings settings)
throws IOException, LoadException, CancelledException {
throw new LoadException("Cannot add GZF to program");
}
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 206c809cfb..94b9eaf764 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
@@ -22,7 +22,6 @@ import ghidra.app.util.Option;
import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.importer.MessageLog;
import ghidra.framework.model.DomainObject;
-import ghidra.framework.model.Project;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.*;
import ghidra.program.model.listing.Program;
@@ -75,7 +74,8 @@ public class IntelHexLoader extends AbstractProgramLoader {
}
@Override
- public String validateOptions(ByteProvider provider, LoadSpec loadSpec, List options, Program program) {
+ public String validateOptions(ByteProvider provider, LoadSpec loadSpec, List options,
+ Program program) {
Address baseAddr = null;
for (Option option : options) {
@@ -142,23 +142,15 @@ public class IntelHexLoader extends AbstractProgramLoader {
}
@Override
- protected List> loadProgram(ByteProvider provider, String programName,
- Project project, String programFolderPath, LoadSpec loadSpec, List options,
- MessageLog log, Object consumer, TaskMonitor monitor)
+ protected List> loadProgram(ImporterSettings settings)
throws IOException, CancelledException {
- LanguageCompilerSpecPair pair = loadSpec.getLanguageCompilerSpec();
- Language importerLanguage = getLanguageService().getLanguage(pair.languageID);
- CompilerSpec importerCompilerSpec =
- importerLanguage.getCompilerSpecByID(pair.compilerSpecID);
- Program prog = createProgram(provider, programName, null, getName(), importerLanguage,
- importerCompilerSpec, consumer);
- List> loadedList =
- List.of(new Loaded<>(prog, programName, project, programFolderPath, consumer));
+ Program prog = createProgram(null, settings);
+ List> loadedList = List.of(new Loaded<>(prog, settings));
boolean success = false;
try {
- loadInto(provider, loadSpec, options, log, prog, monitor);
- createDefaultMemoryBlocks(prog, importerLanguage, log);
+ loadInto(prog, settings);
+ createDefaultMemoryBlocks(prog, settings);
success = true;
return loadedList;
}
@@ -170,16 +162,16 @@ public class IntelHexLoader extends AbstractProgramLoader {
}
@Override
- protected void loadProgramInto(ByteProvider provider, LoadSpec loadSpec,
- List options, MessageLog log, Program prog, TaskMonitor monitor)
+ protected void loadProgramInto(Program prog, ImporterSettings settings)
throws IOException, LoadException, CancelledException {
- Address baseAddr = getBaseAddr(options);
+ Address baseAddr = getBaseAddr(settings.options());
if (baseAddr == null) {
baseAddr = prog.getAddressFactory().getDefaultAddressSpace().getAddress(0);
}
try {
- processIntelHex(provider, options, log, prog, monitor);
+ processIntelHex(settings.provider(), settings.options(), settings.log(), prog,
+ settings.monitor());
}
catch (AddressOverflowException e) {
throw new LoadException(
@@ -265,7 +257,7 @@ public class IntelHexLoader extends AbstractProgramLoader {
@Override
public List getDefaultOptions(ByteProvider provider, LoadSpec loadSpec,
- DomainObject domainObject, boolean loadIntoProgram) {
+ DomainObject domainObject, boolean loadIntoProgram, boolean mirrorFsLayout) {
String blockName = "";
boolean isOverlay = false;
Address baseAddr = null;
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/LoadException.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/LoadException.java
index fa803cf82b..5f3c231d19 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/LoadException.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/LoadException.java
@@ -4,9 +4,9 @@
* 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.
@@ -16,15 +16,9 @@
package ghidra.app.util.opinion;
import java.io.IOException;
-import java.util.List;
-
-import ghidra.app.util.bin.ByteProvider;
-import ghidra.app.util.importer.MessageLog;
-import ghidra.framework.model.Project;
-import ghidra.util.task.TaskMonitor;
/**
- * Thrown when a {@link Loader#load(ByteProvider, String, Project, String, LoadSpec, List,MessageLog, Object, TaskMonitor) load}
+ * Thrown when a {@link Loader#load(ghidra.app.util.opinion.Loader.ImporterSettings) load}
* fails in an expected way. The supplied message should explain the reason.
*/
public class LoadException extends IOException {
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/LoadResults.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/LoadResults.java
index a8fb8b8f08..fd22a26d04 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/LoadResults.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/LoadResults.java
@@ -19,16 +19,16 @@ import java.io.IOException;
import java.util.*;
import java.util.function.Predicate;
-import ghidra.app.util.importer.MessageLog;
-import ghidra.framework.model.*;
+import ghidra.framework.model.DomainObject;
+import ghidra.framework.model.Project;
+import ghidra.util.InvalidNameException;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
/**
* The result of a
- * {@link Loader#load(ghidra.app.util.bin.ByteProvider, String, Project, String, LoadSpec, List, MessageLog, Object, TaskMonitor) load}.
- * A {@link LoadResults} object provides convenient access to and operations on the underlying
- * {@link Loaded} {@link DomainObject}s that got loaded.
+ * {@link Loader#load(ghidra.app.util.opinion.Loader.ImporterSettings)}. Provides convenient
+ * access to and operations on the underlying {@link Loaded} {@link DomainObject}s that got loaded.
*
* @param The type of {@link DomainObject}s that were loaded
*/
@@ -54,29 +54,14 @@ public class LoadResults implements Iterable>,
}
/**
- * Creates a new {@link LoadResults} that contains a new {@link Loaded}
- * {@link DomainObject} created from the given parameters. This new {@link Loaded}
- * {@link DomainObject} is assumed to be the {@link #getPrimary() primary} {@link Loaded}
- * {@link DomainObject}.
+ * Creates a new {@link LoadResults} that contains the given {@link Loaded}
+ * {@link DomainObject}. This {@link Loaded} {@link DomainObject} is assumed to be the
+ * {@link #getPrimary() primary} {@link Loaded} {@link DomainObject}.
*
- * @param domainObject The loaded {@link DomainObject}
- * @param name The name of the loaded {@link DomainObject}. If a
- * {@link #save(TaskMonitor) save} occurs, this will attempted to be used for the resulting
- * {@link DomainFile}'s name.
- * @param project If not null, the project this will get saved to during a
- * {@link #save(TaskMonitor)} operation
- * @param projectFolderPath The project folder path this will get saved to during a
- * {@link #save(TaskMonitor) save} operation. If null or empty, the root project folder will
- * be used.
- * @param consumer A reference to the object "consuming" the returned this
- * {@link LoadResults}, used to ensure the underlying {@link DomainObject}s are only closed
- * when every consumer is done with it (see {@link #close()}). NOTE: Wrapping a
- * {@link DomainObject} in a {@link LoadResults} transfers responsibility of releasing the
- * given {@link DomainObject} to this {@link LoadResults}'s {@link #close()} method.
+ * @param loaded The {@link Loaded} {@link DomainObject}
*/
- public LoadResults(T domainObject, String name, Project project, String projectFolderPath,
- Object consumer) {
- this(List.of(new Loaded(domainObject, name, project, projectFolderPath, consumer)));
+ public LoadResults(Loaded loaded) {
+ this(List.of(loaded));
}
/**
@@ -155,9 +140,11 @@ public class LoadResults implements Iterable>,
* @throws IOException If there was a problem saving. A thrown exception may result in only some
* of the {@link Loaded} elements being saved. It is the responsibility of the caller to clean
* things up appropriately.
+ * @throws InvalidNameException if saving with an invalid name
* @see Loaded#save(TaskMonitor)
*/
- public void save(TaskMonitor monitor) throws CancelledException, IOException {
+ public void save(TaskMonitor monitor)
+ throws CancelledException, IOException, InvalidNameException {
for (Loaded loaded : loadedList) {
loaded.save(monitor);
}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/Loaded.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/Loaded.java
index a0bd7dbc2a..67f54fb66f 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/Loaded.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/Loaded.java
@@ -15,13 +15,26 @@
*/
package ghidra.app.util.opinion;
+import static ghidra.formats.gfilesystem.fileinfo.FileAttributeType.*;
+
import java.io.FileNotFoundException;
import java.io.IOException;
+import java.nio.file.Path;
+import java.util.*;
import java.util.function.Consumer;
import java.util.function.Predicate;
+import org.apache.commons.io.FilenameUtils;
+
+import ghidra.app.util.opinion.Loader.ImporterSettings;
+import ghidra.formats.gfilesystem.*;
+import ghidra.formats.gfilesystem.fileinfo.FileAttributes;
+import ghidra.framework.data.FolderLinkContentHandler;
import ghidra.framework.model.*;
+import ghidra.program.database.ProgramLinkContentHandler;
+import ghidra.program.model.listing.Program;
import ghidra.util.InvalidNameException;
+import ghidra.util.Msg;
import ghidra.util.exception.*;
import ghidra.util.task.TaskMonitor;
@@ -36,8 +49,10 @@ public class Loaded implements AutoCloseable {
protected final T domainObject;
protected final String name;
+ protected FSRL fsrl;
protected Project project;
- protected String projectFolderPath;
+ protected String projectRootPath;
+ protected boolean mirrorFsLayout;
protected Object loadedConsumer;
protected DomainFile domainFile;
@@ -48,26 +63,44 @@ public class Loaded implements AutoCloseable {
* This object needs to be {@link #close() closed} when done with it.
*
* @param domainObject The loaded {@link DomainObject}
- * @param name The name of the loaded {@link DomainObject}. If a {@link #save(TaskMonitor)}
- * occurs, this will attempted to be used for the resulting {@link DomainFile}'s name.
- * @param project If not null, the project this will get saved to during a
+ * @param name The name of the loaded {@link DomainObject}. Path information that appears at the
+ * beginning the name will be appended to the {@code projectRootPath} during a
+ * {@link #save(TaskMonitor) operation}.
+ * @param fsrl The {@link FSRL} of the loaded {@link DomainObject}
+ * @param project If not {@code null}, the project this will get saved to during a
* {@link #save(TaskMonitor)} operation
- * @param projectFolderPath The project folder path this will get saved to during a
- * {@link #save(TaskMonitor)} operation. If null or empty, the root project folder will be
- * used.
+ * @param projectRootPath The project folder path that all {@link Loaded} {@link DomainObject}s
+ * will be {@link #save(TaskMonitor) saved} relative to. If {@code null}, "/" will be used.
+ * @param mirrorFsLayout True if the filesystem layout should be mirrored when
+ * {@link #save(TaskMonitor) saving}; otherwise, false
* @param consumer A reference to the object "consuming" the returned {@link Loaded}
* {@link DomainObject}, used to ensure the underlying {@link DomainObject} is only closed
* when every consumer is done with it (see {@link #close()}). NOTE: Wrapping a
* {@link DomainObject} in a {@link Loaded} transfers responsibility of releasing the
* given {@link DomainObject} to this {@link Loaded}'s {@link #close()} method.
*/
- public Loaded(T domainObject, String name, Project project, String projectFolderPath,
- Object consumer) {
+ public Loaded(T domainObject, String name, FSRL fsrl, Project project, String projectRootPath,
+ boolean mirrorFsLayout, Object consumer) {
this.domainObject = domainObject;
this.name = name;
+ this.fsrl = fsrl;
this.project = project;
+ this.mirrorFsLayout = mirrorFsLayout;
this.loadedConsumer = consumer;
- setProjectFolderPath(projectFolderPath);
+ setProjectFolderPath(projectRootPath);
+ }
+
+ /**
+ * Creates a new {@link Loaded} object.
+ *
+ * This object needs to be {@link #close() closed} when done with it.
+ *
+ * @param domainObject The loaded {@link DomainObject}
+ * @param settings The {@link Loader.ImporterSettings}.
+ */
+ public Loaded(T domainObject, ImporterSettings settings) {
+ this(domainObject, settings.importName(), settings.provider().getFSRL(), settings.project(),
+ settings.projectRootPath(), settings.mirrorFsLayout(), settings.consumer());
}
/**
@@ -165,25 +198,26 @@ public class Loaded implements AutoCloseable {
* @return the project folder path
*/
public String getProjectFolderPath() {
- return projectFolderPath;
+ return projectRootPath;
}
/**
* Sets the project folder path this will get saved to during a {@link #save(TaskMonitor)}
* operation.
*
- * @param projectFolderPath The project folder path this will get saved to during a
- * {@link #save(TaskMonitor)} operation. If null or empty, the root project folder will be
- * used.
+ * @param projectRootPath The project folder path that all {@link Loaded} {@link DomainObject}s
+ * will be saved relative to. If {@code null}, "/" will be used.
*/
- public void setProjectFolderPath(String projectFolderPath) {
- if (projectFolderPath == null || projectFolderPath.isBlank()) {
- projectFolderPath = "/";
+ public void setProjectFolderPath(String projectRootPath) {
+ if (projectRootPath == null || projectRootPath.isBlank()) {
+ projectRootPath = "/";
}
- else if (!projectFolderPath.endsWith("/")) {
- projectFolderPath += "/";
+ else if (!projectRootPath.endsWith("/")) {
+ projectRootPath += "/";
}
- this.projectFolderPath = projectFolderPath;
+
+ this.projectRootPath =
+ mirrorFsLayout ? FSUtilities.mirroredProjectPath(projectRootPath) : projectRootPath;
}
/**
@@ -199,11 +233,16 @@ public class Loaded implements AutoCloseable {
* @return The {@link DomainFile} where the save happened
* @throws CancelledException if the operation was cancelled
* @throws ClosedException if the loaded {@link DomainObject} was already closed
- * @throws IOException If there was an IO-related error, an invalid name was specified, or it
- * was already successfully saved and still exists
+ * @throws IOException If there was an IO-related error, a project wasn't specified, an invalid
+ * name was specified, or it was already successfully saved and still exists
+ * @throws InvalidNameException if saving with an invalid name
*/
public DomainFile save(TaskMonitor monitor)
- throws CancelledException, ClosedException, IOException {
+ throws CancelledException, ClosedException, IOException, InvalidNameException {
+
+ if (project == null) {
+ throw new IOException("Cannot save to null project");
+ }
if (domainObject.isClosed()) {
throw new ClosedException(
@@ -221,25 +260,27 @@ public class Loaded implements AutoCloseable {
domainFile = null;
}
+ if (mirrorFsLayout && fsrl != null) {
+ domainFile = mirror(monitor);
+ return domainFile;
+ }
+
int uniqueNameIndex = 0;
- String uniqueName = name;
- try {
- DomainFolder programFolder = ProjectDataUtils.createDomainFolderPath(
- project.getProjectData().getRootFolder(), projectFolderPath);
- while (!monitor.isCancelled()) {
- try {
- domainFile = programFolder.createFile(uniqueName, domainObject, monitor);
- return domainFile;
- }
- catch (DuplicateFileException e) {
- uniqueName = name + "." + uniqueNameIndex;
- ++uniqueNameIndex;
- }
+ String uniqueName = FilenameUtils.getName(name);
+ DomainFolder programFolder =
+ ProjectDataUtils.createDomainFolderPath(project.getProjectData().getRootFolder(),
+ FSUtilities.appendPath(projectRootPath, FilenameUtils.getFullPath(name)));
+ while (!monitor.isCancelled()) {
+ try {
+ domainFile = programFolder.createFile(uniqueName, domainObject, monitor);
+ return domainFile;
+ }
+ catch (DuplicateFileException e) {
+ uniqueName = name + "." + uniqueNameIndex;
+ ++uniqueNameIndex;
}
}
- catch (InvalidNameException e) {
- throw new IOException(e);
- }
+
throw new CancelledException();
}
@@ -294,4 +335,124 @@ public class Loaded implements AutoCloseable {
public String toString() {
return getProjectFolderPath() + getName();
}
+
+ /**
+ * A project link and its associated metadata that was created during the mirror process
+ *
+ * @param linkFile The {@link DomainFile project link}. It may link to a {@link DomainFile} or
+ * a {@link DomainFolder}.
+ * @param projectLinkTarget The project path of the link's target
+ * @param symlink The original target value of the link. It may be either relative, or absolute.
+ * @param relative True if the {@code symlink} is relative; false if it is absolute
+ */
+ private record MirroredLink(DomainFile linkFile, String projectLinkTarget, String symlink,
+ boolean relative) {}
+
+ /**
+ * Saves the loaded {@link DomainObject} to the given {@link Project}, mirroring this object's
+ * filesystem path in the project. Depending on the nature of the filesystem path, project
+ * folder and/or file links may be created during the save.
+ *
+ * @param monitor A cancelable task monitor
+ * @return The {@link DomainFile} where the save happened
+ * @throws CancelledException if the operation was cancelled
+ * @throws IOException If there was an IO-related error
+ * @throws InvalidNameException if saving with an invalid name
+ */
+ private DomainFile mirror(TaskMonitor monitor)
+ throws IOException, InvalidNameException, CancelledException {
+ DomainFolder mirrorRootProjectFolder = ProjectDataUtils
+ .createDomainFolderPath(project.getProjectData().getRootFolder(), projectRootPath);
+ String currentPath = null;
+ Set processedPaths = new HashSet<>();
+ String[] pathElements = FSUtilities.splitPath(fsrl.getPath());
+ try (RefdFile ref = FileSystemService.getInstance().getRefdFile(fsrl, monitor)) {
+ for (int i = 0; i < pathElements.length; i++) {
+ String pathElement = pathElements[i];
+ if (i == 0) {
+ if (!pathElement.isEmpty()) {
+ throw new IOException("FSRL '%s' is not absolute!".formatted(fsrl));
+ }
+ currentPath = "/";
+ continue;
+ }
+ currentPath = FSUtilities.appendPath(currentPath, pathElement);
+ if (processedPaths.contains(currentPath)) {
+ continue;
+ }
+ GFileSystem fs = ref.fsRef.getFilesystem();
+ GFile currentFile = fs.lookup(currentPath);
+ String currentParentDirPath = currentFile.getParentFile().getPath();
+ DomainFolder parentProjectFolder = ProjectDataUtils.getDomainFolder(
+ mirrorRootProjectFolder, FSUtilities.mirroredProjectPath(currentParentDirPath));
+ FileAttributes fattrs = fs.getFileAttributes(currentFile, monitor);
+ String symlinkDest = fattrs.get(SYMLINK_DEST_ATTR, String.class, null);
+ if (symlinkDest != null) {
+ MirroredLink mirroredLink =
+ mirrorLinkInProject(currentFile, symlinkDest, parentProjectFolder, monitor);
+ String symlink = mirroredLink.relative()
+ ? FSUtilities.appendPath(currentParentDirPath, mirroredLink.symlink())
+ : mirroredLink.symlink();
+ symlink = Path.of(symlink).normalize().toString(); // fixup any '.' and '..'
+ String[] oldElements = pathElements;
+ String[] newElements = FSUtilities.splitPath(symlink);
+ pathElements =
+ Arrays.copyOf(newElements, newElements.length + oldElements.length - i - 1);
+ System.arraycopy(oldElements, i + 1, pathElements, newElements.length,
+ pathElements.length - newElements.length);
+ i = -1;
+ }
+ else if (currentFile.isDirectory()) {
+ ProjectDataUtils.createDomainFolderPath(mirrorRootProjectFolder,
+ FSUtilities.mirroredProjectPath(currentPath));
+ processedPaths.add((currentPath));
+ }
+ else {
+ try {
+ if (domainObject instanceof Program program) {
+ program.withTransaction("Updating Program Info", () -> {
+ program.setExecutablePath(FSUtilities.appendPath(
+ parentProjectFolder.getPathname(), currentFile.getName()));
+ FSRL.writeToProgramInfo(program, currentFile.getFSRL());
+ });
+ }
+ return parentProjectFolder.createFile(currentFile.getName(), domainObject,
+ monitor);
+ }
+ catch (DuplicateFileException e) {
+ DomainFile f = parentProjectFolder.getFile(currentFile.getName());
+ Msg.warn(this, "Skipping save of existing file: " + f);
+ return f;
+ }
+ }
+ }
+ throw new IOException("Path did not point to a file!");
+ }
+ }
+
+ /**
+ * Creates a file or folder link in the project
+ *
+ * @param file The {@link GFile link file}
+ * @param linkDest The link destination (relative or absolute)
+ * @param folder The {@link DomainFolder} to create the link in
+ * @param monitor A cancelable task monitor
+ * @return The newly created {@link MirroredLink project link}
+ * @throws IOException if an IO-related error occurred
+ */
+ private MirroredLink mirrorLinkInProject(GFile file, String linkDest, DomainFolder folder,
+ TaskMonitor monitor) throws IOException {
+ boolean relative = FilenameUtils.getPrefixLength(linkDest) == 0;
+ String projectLinkTarget = FSUtilities.mirroredProjectPath(relative
+ ? FSUtilities.appendPath(projectRootPath,
+ FilenameUtils.getFullPath(file.getPath()), linkDest)
+ : FSUtilities.appendPath(projectRootPath, linkDest));
+ DomainFile df = folder.getFile(file.getName());
+ if (df == null) {
+ df = folder.createLinkFile(project.getProjectData(), projectLinkTarget, relative,
+ file.getName(), file.isDirectory() ? FolderLinkContentHandler.INSTANCE
+ : ProgramLinkContentHandler.INSTANCE);
+ }
+ return new MirroredLink(df, projectLinkTarget, linkDest, relative);
+ }
}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/LoadedOpen.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/LoadedOpen.java
index de8d63ab4d..cf2f9d3354 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/LoadedOpen.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/LoadedOpen.java
@@ -15,6 +15,7 @@
*/
package ghidra.app.util.opinion;
+import ghidra.formats.gfilesystem.FSRL;
import ghidra.framework.model.DomainFile;
import ghidra.framework.model.DomainObject;
import ghidra.util.task.TaskMonitor;
@@ -32,6 +33,7 @@ public class LoadedOpen extends Loaded {
*
* @param domainObject The loaded {@link DomainObject}
* @param domainFile The {@link DomainFile} associated with the loaded {@link DomainObject}
+ * @param fsrl The {@link FSRL} of the loaded {@link DomainObject}
* @param consumer A reference to the object "consuming" the returned {@link Loaded}
* {@link DomainObject}, used to ensure the underlying {@link DomainObject} is only closed
* when every consumer is done with it (see {@link #close()}). NOTE: Wrapping a
@@ -39,9 +41,10 @@ public class LoadedOpen extends Loaded {
* given {@link DomainObject} to this {@link Loaded}'s {@link #close()} method.
* @throws LoadException if the given {@link DomainFile} is not open
*/
- public LoadedOpen(T domainObject, DomainFile domainFile, Object consumer) throws LoadException {
- super(domainObject, domainFile.getName(), null, domainFile.getParent().getPathname(),
- consumer);
+ public LoadedOpen(T domainObject, DomainFile domainFile, FSRL fsrl, Object consumer)
+ throws LoadException {
+ super(domainObject, domainFile.getName(), fsrl, null, domainFile.getParent().getPathname(),
+ false, consumer);
this.domainFile = domainFile;
if (!domainFile.isOpen()) {
throw new LoadException(domainFile + " is not open");
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/Loader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/Loader.java
index 33b4affd15..4f73ec354e 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/Loader.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/Loader.java
@@ -19,6 +19,8 @@ import java.io.IOException;
import java.util.Collection;
import java.util.List;
+import org.apache.commons.io.FilenameUtils;
+
import ghidra.app.util.Option;
import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.importer.MessageLog;
@@ -59,6 +61,52 @@ public interface Loader extends ExtensionPoint, Comparable {
public static boolean loggingDisabled =
SystemUtilities.getBooleanProperty("disable.loader.logging", false);
+ /**
+ * A {@link Loader} configuration
+ *
+ * @param provider The bytes to load.
+ * @param importName The name for the primary {@link Loaded} {@link DomainObject}. Path
+ * information that appears at the beginning the name will be appended to the
+ * {@code projectRootPath} during the {@link LoadResults#save(TaskMonitor) saving process}.
+ * @param project The {@link Project}. Loaders can use this to take advantage of existing
+ * {@link DomainFolder}s and {@link DomainFile}s to do custom behaviors such as loading
+ * libraries. A {@link Project} is also required during the
+ * {@link LoadResults#save(TaskMonitor) saving process}. Could be {@code null} if there is no
+ * project.
+ * @param projectRootPath The project folder path that all {@link Loaded} {@link DomainObject}s
+ * will be {@link LoadResults#save(TaskMonitor) saved} relative to. If {@code null}, "/" will
+ * be used.
+ * @param mirrorFsLayout True if the filesystem layout should be mirrored when
+ * {@link LoadResults#save(TaskMonitor) saving}; otherwise, false
+ * @param loadSpec The {@link LoadSpec} to use during load.
+ * @param options The load options.
+ * @param consumer A reference to the object "consuming" the returned {@link LoadResults}, used
+ * to ensure the underlying {@link Program}s are only closed when every consumer is done
+ * with it (see {@link LoadResults#close()}).
+ * @param log The message log.
+ * @param monitor A task monitor.
+ */
+ public record ImporterSettings(ByteProvider provider, String importName, Project project,
+ String projectRootPath, boolean mirrorFsLayout, LoadSpec loadSpec, List options,
+ Object consumer, MessageLog log, TaskMonitor monitor) {
+
+ /**
+ * {@return The name portion of the {@code importName}, stripping off any leading path
+ * information that may be present}
+ */
+ public String importNameOnly() {
+ return FilenameUtils.getName(importName);
+ }
+
+ /**
+ * {@return The path portion of the {@code importName} if present, stripping off the
+ * trailing name (could be the empty string)}
+ */
+ public String importPathOnly() {
+ return FilenameUtils.getFullPath(importName);
+ }
+ }
+
/**
* If this {@link Loader} supports loading the given {@link ByteProvider}, this methods returns
* a {@link Collection} of all supported {@link LoadSpec}s that contain discovered load
@@ -88,26 +136,7 @@ public interface Loader extends ExtensionPoint, Comparable {
* It is also the responsibility of the caller to close the returned {@link Loaded}
* {@link DomainObject}s with {@link LoadResults#close()} when they are no longer needed.
*
- * @param provider The bytes to load.
- * @param loadedName A suggested name for the primary {@link Loaded} {@link DomainObject}.
- * This is just a suggestion, and a {@link Loader} implementation reserves the right to change
- * it. The {@link LoadResults} should be queried for their true names using
- * {@link Loaded#getName()}.
- * @param project The {@link Project}. Loaders can use this to take advantage of existing
- * {@link DomainFolder}s and {@link DomainFile}s to do custom behaviors such as loading
- * libraries. Could be null if there is no project.
- * @param projectFolderPath A suggested project folder path for the {@link Loaded}
- * {@link DomainObject}s. This is just a suggestion, and a {@link Loader} implementation
- * reserves the right to change it for each {@link Loaded} result. The {@link LoadResults}
- * should be queried for their true project folder paths using
- * {@link Loaded#getProjectFolderPath()}.
- * @param loadSpec The {@link LoadSpec} to use during load.
- * @param options The load options.
- * @param messageLog The message log.
- * @param consumer A reference to the object "consuming" the returned {@link LoadResults}, used
- * to ensure the underlying {@link Program}s are only closed when every consumer is done
- * with it (see {@link LoadResults#close()}).
- * @param monitor A task monitor.
+ * @param settings The {@link ImporterSettings}.
* @return The {@link LoadResults} which contains one or more {@link Loaded}
* {@link DomainObject}s (created but not saved).
* @throws LoadException if the load failed in an expected way
@@ -116,27 +145,20 @@ public interface Loader extends ExtensionPoint, Comparable {
* @throws VersionException if the load process tried to open an existing {@link DomainFile}
* which was created with a newer or unsupported version of Ghidra
*/
- public LoadResults extends DomainObject> load(ByteProvider provider, String loadedName,
- Project project, String projectFolderPath, LoadSpec loadSpec, List options,
- MessageLog messageLog, Object consumer, TaskMonitor monitor) throws IOException,
- CancelledException, VersionException, LoadException;
+ public LoadResults extends DomainObject> load(ImporterSettings settings)
+ throws IOException, CancelledException, VersionException, LoadException;
/**
* Loads bytes into the specified {@link Program}. This method will not create any new
* {@link Program}s. It is only for adding to an existing {@link Program}.
*
- * @param provider The bytes to load into the {@link Program}.
- * @param loadSpec The {@link LoadSpec} to use during load.
- * @param options The load options.
- * @param messageLog The message log.
* @param program The {@link Program} to load into.
- * @param monitor A cancelable task monitor.
+ * @param settings The {@link ImporterSettings}.
* @throws LoadException if the load failed in an expected way.
* @throws IOException if there was an IO-related problem loading.
* @throws CancelledException if the user cancelled the load.
*/
- public void loadInto(ByteProvider provider, LoadSpec loadSpec, List options,
- MessageLog messageLog, Program program, TaskMonitor monitor)
+ public void loadInto(Program program, ImporterSettings settings)
throws IOException, LoadException, CancelledException;
/**
@@ -147,10 +169,12 @@ public interface Loader extends ExtensionPoint, Comparable {
* @param domainObject The {@link DomainObject} being loaded.
* @param loadIntoProgram True if the load is adding to an existing {@link DomainObject};
* otherwise, false.
+ * @param mirrorFsLayout True if the filesystem layout should be mirrored when loading;
+ * otherwise, false
* @return A list of the {@link Loader}'s default options.
*/
public List getDefaultOptions(ByteProvider provider, LoadSpec loadSpec,
- DomainObject domainObject, boolean loadIntoProgram);
+ DomainObject domainObject, boolean loadIntoProgram, boolean mirrorFsLayout);
/**
* Validates the {@link Loader}'s options and returns null if all options are valid; otherwise,
@@ -160,7 +184,7 @@ public interface Loader extends ExtensionPoint, Comparable {
* @param loadSpec The proposed {@link LoadSpec}.
* @param options The list of {@link Option}s to validate.
* @param program existing program if the loader is adding to an existing program. If it is
- * a fresh import, then this will be null.
+ * a fresh import, then this will be null.
* @return null if all {@link Option}s are valid; otherwise, an error message describing the
* problem is returned.
*/
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/MSCoffLoader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/MSCoffLoader.java
index 61903baab3..34c8d1e231 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/MSCoffLoader.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/MSCoffLoader.java
@@ -15,6 +15,8 @@
*/
package ghidra.app.util.opinion;
+import java.util.Comparator;
+
import ghidra.app.util.bin.format.coff.CoffSectionHeader;
import ghidra.app.util.bin.format.pe.SectionHeader;
@@ -32,8 +34,8 @@ public class MSCoffLoader extends CoffLoader {
}
@Override
- protected boolean isCaseInsensitiveLibraryFilenames() {
- return true;
+ protected Comparator getLibraryNameComparator() {
+ return String.CASE_INSENSITIVE_ORDER;
}
@Override
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/MachoLoader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/MachoLoader.java
index d7d278888b..85e61f7c23 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/MachoLoader.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/MachoLoader.java
@@ -109,11 +109,14 @@ public class MachoLoader extends AbstractLibrarySupportLoader {
}
@Override
- public void load(ByteProvider provider, LoadSpec loadSpec, List options,
- Program program, TaskMonitor monitor, MessageLog log) throws IOException {
+ public void load(Program program, ImporterSettings settings) throws IOException {
+
+ ByteProvider provider = settings.provider();
+ MessageLog log = settings.log();
+ TaskMonitor monitor = settings.monitor();
if (isUniveralBinary(provider)) {
- provider = matchUniversalBinaryProvider(provider, loadSpec, monitor);
+ provider = matchUniversalBinaryProvider(provider, settings.loadSpec(), monitor);
}
try {
@@ -142,9 +145,9 @@ public class MachoLoader extends AbstractLibrarySupportLoader {
@Override
public List getDefaultOptions(ByteProvider provider, LoadSpec loadSpec,
- DomainObject domainObject, boolean loadIntoProgram) {
- List list =
- super.getDefaultOptions(provider, loadSpec, domainObject, loadIntoProgram);
+ DomainObject domainObject, boolean loadIntoProgram, boolean mirrorFsLayout) {
+ List list = super.getDefaultOptions(provider, loadSpec, domainObject,
+ loadIntoProgram, mirrorFsLayout);
if (!loadIntoProgram) {
list.add(new Option(REEXPORT_OPTION_NAME, REEXPORT_OPTION_DEFAULT,
Boolean.class, Loader.COMMAND_LINE_ARG_PREFIX + "-reexport"));
@@ -174,16 +177,16 @@ public class MachoLoader extends AbstractLibrarySupportLoader {
}
@Override
- protected boolean isValidSearchPath(FSRL fsrl, LoadSpec loadSpec, TaskMonitor monitor)
+ protected boolean isValidSearchPath(FSRL fsrl, ImporterSettings settings)
throws CancelledException {
FileSystemService fsService = FileSystemService.getInstance();
- try (ByteProvider provider = fsService.getByteProvider(fsrl, loggingDisabled, monitor)) {
+ try (ByteProvider provider = fsService.getByteProvider(fsrl, false, settings.monitor())) {
if (!DyldCacheUtils.isDyldCache(provider)) {
return true;
}
DyldCacheHeader header = new DyldCacheHeader(new BinaryReader(provider, true));
DyldArchitecture dyld = header.getArchitecture();
- LanguageCompilerSpecPair lcs = loadSpec.getLanguageCompilerSpec();
+ LanguageCompilerSpecPair lcs = settings.loadSpec().getLanguageCompilerSpec();
String processor = lcs.getLanguage().getProcessor().toString().toLowerCase();
boolean is64bit = lcs.getLanguage()
.getAddressFactory()
@@ -267,10 +270,10 @@ public class MachoLoader extends AbstractLibrarySupportLoader {
* {@inheritDoc}
*/
@Override
- protected FSRL resolveLibraryFile(GFileSystem fs, String library) throws IOException {
- FSRL fsrl = super.resolveLibraryFile(fs, library);
- if (fsrl != null) {
- return fsrl;
+ protected GFile lookupLibraryInFs(String library, GFileSystem fs) throws IOException {
+ GFile f = super.lookupLibraryInFs(library, fs);
+ if (f != null) {
+ return f;
}
String libraryParentPath = FilenameUtils.getFullPath(library);
String libraryName = FilenameUtils.getName(library);
@@ -278,13 +281,13 @@ public class MachoLoader extends AbstractLibrarySupportLoader {
if (libraryParentDir != null) {
for (GFile file : fs.getListing(libraryParentDir)) {
if (file.isDirectory() && file.getName().equals("Versions")) {
- String versionsPath = joinPaths(libraryParentPath, file.getName());
+ String versionsPath = FSUtilities.appendPath(libraryParentPath, file.getName());
List versionListion = fs.getListing(file);
if (!versionListion.isEmpty()) {
GFile specificVersionDir = versionListion.get(0);
if (specificVersionDir.isDirectory()) {
- return resolveLibraryFile(fs,
- joinPaths(versionsPath, specificVersionDir.getName(), libraryName));
+ return lookupLibraryInFs(FSUtilities.appendPath(versionsPath,
+ specificVersionDir.getName(), libraryName), fs);
}
}
}
@@ -292,13 +295,56 @@ public class MachoLoader extends AbstractLibrarySupportLoader {
continue;
}
if (file.getName().equals(libraryName)) {
- return file.getFSRL();
+ return file;
}
}
}
return null;
}
+ /**
+ * Special Mach-O library file resolver to account for a "Versions" subdirectory being inserted
+ * in the library lookup path. For example, a reference to:
+ *
+ * {@code /System/Library/Frameworks/Foundation.framework/Foundation}
+ *
+ * might be found at:
+ *
+ * {@code /System/Library/Frameworks/Foundation.framework/Versions/C/Foundation}
+ *
+ * {@inheritDoc}
+ */
+ @Override
+ protected DomainFile lookupLibraryInFolder(String libraryName, DomainFolder folder) {
+ DomainFolder versionsFolder = folder.getFolder("Versions");
+ if (versionsFolder != null) {
+ DomainFolder[] versions = versionsFolder.getFolders();
+ if (versions.length > 0) {
+ folder = versions[0];
+ }
+ }
+ return super.lookupLibraryInFolder(libraryName, folder);
+ }
+
+ /**
+ * Special Mach-O library {@link Comparator} to account for a "Versions" subdirectory being
+ * inserted in the library lookup path. For example, a reference to:
+ *
+ * {@code /System/Library/Frameworks/Foundation.framework/Foundation}
+ *
+ * might be found at:
+ *
+ * {@code /System/Library/Frameworks/Foundation.framework/Versions/C/Foundation}
+ *
+ * {@inheritDoc}
+ */
+ @Override
+ protected Comparator getLibraryNameComparator() {
+ String versionRegex = "Versions/.+/";
+ return (s1, s2) -> s1.replaceAll(versionRegex, "")
+ .compareTo(s2.replaceAll(versionRegex, ""));
+ }
+
/**
* {@inheritDoc}
*
@@ -306,11 +352,11 @@ public class MachoLoader extends AbstractLibrarySupportLoader {
* set and the Mach-O actually has {@code LC_REEXPORT_DYLIB} entries.
*/
@Override
- protected boolean shouldSearchAllPaths(Program program, List options, MessageLog log) {
- if (super.shouldSearchAllPaths(program, options, log)) {
+ protected boolean shouldSearchAllPaths(Program program, ImporterSettings settings) {
+ if (super.shouldSearchAllPaths(program, settings)) {
return true;
}
- if (shouldPerformReexports(options)) {
+ if (shouldPerformReexports(settings)) {
try {
Symbol header =
program.getSymbolTable().getSymbols(MachoProgramBuilder.HEADER_SYMBOL).next();
@@ -323,8 +369,9 @@ public class MachoLoader extends AbstractLibrarySupportLoader {
}
}
catch (Exception e) {
- log.appendMsg("Failed to parse Mach-O header for: '%s': %s"
- .formatted(program.getName(), e.getMessage()));
+ settings.log()
+ .appendMsg("Failed to parse Mach-O header for: '%s': %s"
+ .formatted(program.getName(), e.getMessage()));
}
}
return false;
@@ -339,17 +386,16 @@ public class MachoLoader extends AbstractLibrarySupportLoader {
* depth would have prevented their save as a normal library)
*/
@Override
- protected void processLibrary(Program lib, String libName, FSRL libFsrl, ByteProvider provider,
- Queue unprocessed, int depth, LoadSpec loadSpec,
- List options, MessageLog log, TaskMonitor monitor)
+ protected void processLibrary(Program lib, String libName, FSRL libFsrl,
+ Queue unprocessed, int depth, ImporterSettings settings)
throws IOException, CancelledException {
- if (!shouldPerformReexports(options)) {
+ if (!shouldPerformReexports(settings)) {
return;
}
try {
- for (String path : getReexportPaths(lib, log)) {
+ for (String path : getReexportPaths(lib, settings.log())) {
unprocessed.add(new UnprocessedLibrary(path, depth, depth == 1));
}
}
@@ -364,19 +410,20 @@ public class MachoLoader extends AbstractLibrarySupportLoader {
* Adds reexported symbols to each {@link Loaded} {@link Program}.
*/
@Override
- protected void postLoadProgramFixups(List> loadedPrograms, Project project,
- LoadSpec loadSpec, List options, MessageLog log, TaskMonitor monitor)
- throws CancelledException, IOException {
+ protected void postLoadProgramFixups(List> loadedPrograms,
+ ImporterSettings settings) throws CancelledException, IOException {
- if (shouldPerformReexports(options)) {
+ MessageLog log = settings.log();
+ TaskMonitor monitor = settings.monitor();
+
+ if (shouldPerformReexports(settings)) {
- List searchFolders =
- getLibrarySearchFolders(loadedPrograms, project, options, log);
+ List searchFolders = getLibrarySearchFolders(loadedPrograms, settings);
Program firstProgram = loadedPrograms.getFirst().getDomainObject(this);
List searchPaths;
try {
- searchPaths = getLibrarySearchPaths(firstProgram, loadSpec, options, log, monitor);
+ searchPaths = getLibrarySearchPaths(firstProgram, settings);
}
finally {
firstProgram.release(this);
@@ -389,8 +436,7 @@ public class MachoLoader extends AbstractLibrarySupportLoader {
Program program = loadedProgram.getDomainObject(this);
int id = program.startTransaction("Reexporting");
try {
- reexport(program, loadedPrograms, searchFolders, searchPaths, options, monitor,
- log);
+ reexport(program, loadedPrograms, searchFolders, searchPaths, settings);
}
catch (Exception e) {
log.appendException(e);
@@ -402,8 +448,7 @@ public class MachoLoader extends AbstractLibrarySupportLoader {
}
}
- super.postLoadProgramFixups(loadedPrograms, project, loadSpec, options, log,
- monitor);
+ super.postLoadProgramFixups(loadedPrograms, settings);
}
/**
@@ -507,11 +552,12 @@ public class MachoLoader extends AbstractLibrarySupportLoader {
/**
* Checks to see if reexports should be performed
*
- * @param options a {@link List} of {@link Option}s
+ * @param settings The {@link Loader.ImporterSettings}
* @return True if reexports should be performed; otherwise, false
*/
- private boolean shouldPerformReexports(List options) {
- return OptionUtils.getOption(REEXPORT_OPTION_NAME, options, REEXPORT_OPTION_DEFAULT);
+ private boolean shouldPerformReexports(ImporterSettings settings) {
+ return OptionUtils.getOption(REEXPORT_OPTION_NAME, settings.options(),
+ REEXPORT_OPTION_DEFAULT);
}
/**
@@ -550,16 +596,16 @@ public class MachoLoader extends AbstractLibrarySupportLoader {
* @param searchFolders A {@link List} of project folders that may contain already-loaded
* {@link Program}s with reexportable symbols
* @param searchPaths A {@link List} of file system search paths that will be searched
- * @param options The load options
- * @param monitor A cancelable task monitor
- * @param log The log
+ * @param settings The {@link Loader.ImporterSettings}
* @throws CancelledException if the user cancelled the load operation
* @throws IOException if there was an IO-related error during the load
*/
private void reexport(Program program, List> loadedPrograms,
List searchFolders, List searchPaths,
- List options, TaskMonitor monitor, MessageLog log)
+ ImporterSettings settings)
throws CancelledException, Exception {
+ MessageLog log = settings.log();
+ TaskMonitor monitor = settings.monitor();
for (String path : getReexportPaths(program, log)) {
monitor.checkCancelled();
@@ -572,7 +618,7 @@ public class MachoLoader extends AbstractLibrarySupportLoader {
if (lib == null) {
for (DomainFolder searchFolder : searchFolders) {
DomainFile df =
- findLibraryInProject(path, searchFolder, searchPaths, options, monitor);
+ findLibraryInProject(path, searchFolder, searchPaths, settings);
if (df != null) {
DomainObject obj = df.getDomainObject(this, true, true, monitor);
if (obj instanceof Program p) {
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/MapLoader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/MapLoader.java
index 9d85539bf7..8fe427f7a3 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/MapLoader.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/MapLoader.java
@@ -19,7 +19,6 @@ import java.io.*;
import java.text.ParseException;
import java.util.*;
-import ghidra.app.util.Option;
import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.importer.MessageLog;
import ghidra.program.model.address.Address;
@@ -92,8 +91,11 @@ public class MapLoader extends AbstractProgramWrapperLoader {
}
@Override
- public void load(ByteProvider provider, LoadSpec loadSpec, List options, Program prog,
- TaskMonitor monitor, MessageLog log) throws IOException, CancelledException {
+ public void load(Program prog, ImporterSettings settings)
+ throws IOException, CancelledException {
+
+ MessageLog log = settings.log();
+ TaskMonitor monitor = settings.monitor();
if (!prog.getExecutableFormat().equals(PeLoader.PE_NAME)) {
throw new IOException("Program must be a " + PeLoader.PE_NAME);
@@ -103,7 +105,7 @@ public class MapLoader extends AbstractProgramWrapperLoader {
AddressSpace space = prog.getAddressFactory().getDefaultAddressSpace();
int successCount = 0;
- List symbols = parseMapFile(provider, monitor, log);
+ List symbols = parseMapFile(settings.provider(), monitor, log);
monitor.initialize(symbols.size(), "Creating symbols...");
for (MapSymbol symbol : symbols) {
monitor.increment();
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 7f77cbdc2a..3a8bb35ee5 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
@@ -23,7 +23,6 @@ import ghidra.app.util.Option;
import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.importer.MessageLog;
import ghidra.framework.model.DomainObject;
-import ghidra.framework.model.Project;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.*;
import ghidra.program.model.listing.Program;
@@ -160,23 +159,15 @@ public class MotorolaHexLoader extends AbstractProgramLoader {
}
@Override
- protected List> loadProgram(ByteProvider provider, String programName,
- Project project, String programFolderPath, LoadSpec loadSpec, List options,
- MessageLog log, Object consumer, TaskMonitor monitor)
+ protected List> loadProgram(ImporterSettings settings)
throws IOException, LoadException, CancelledException {
- LanguageCompilerSpecPair pair = loadSpec.getLanguageCompilerSpec();
- Language importerLanguage = getLanguageService().getLanguage(pair.languageID);
- CompilerSpec importerCompilerSpec =
- importerLanguage.getCompilerSpecByID(pair.compilerSpecID);
- Program prog = createProgram(provider, programName, null, getName(), importerLanguage,
- importerCompilerSpec, consumer);
- List> loadedList =
- List.of(new Loaded<>(prog, programName, project, programFolderPath, consumer));
+ Program prog = createProgram(null, settings);
+ List> loadedList = List.of(new Loaded<>(prog, settings));
boolean success = false;
try {
- loadInto(provider, loadSpec, options, log, prog, monitor);
- createDefaultMemoryBlocks(prog, importerLanguage, log);
+ loadInto(prog, settings);
+ createDefaultMemoryBlocks(prog, settings);
success = true;
return loadedList;
}
@@ -188,16 +179,16 @@ public class MotorolaHexLoader extends AbstractProgramLoader {
}
@Override
- protected void loadProgramInto(ByteProvider provider, LoadSpec loadSpec,
- List options, MessageLog log, Program prog, TaskMonitor monitor)
+ protected void loadProgramInto(Program prog, ImporterSettings settings)
throws IOException, LoadException, CancelledException {
- Address baseAddr = getBaseAddr(options);
+ Address baseAddr = getBaseAddr(settings.options());
if (baseAddr == null) {
baseAddr = prog.getAddressFactory().getDefaultAddressSpace().getAddress(0);
}
try {
- processMotorolaHex(provider, options, prog, baseAddr, monitor);
+ processMotorolaHex(settings.provider(), settings.options(), prog, baseAddr,
+ settings.monitor());
}
catch (AddressOverflowException e) {
throw new LoadException(
@@ -426,7 +417,7 @@ public class MotorolaHexLoader extends AbstractProgramLoader {
@Override
public List getDefaultOptions(ByteProvider provider, LoadSpec loadSpec,
- DomainObject domainObject, boolean loadIntoProgram) {
+ DomainObject domainObject, boolean loadIntoProgram, boolean mirrorFsLayout) {
String blockName = "";
boolean isOverlay = false;
Address baseAddr = null;
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/MzLoader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/MzLoader.java
index 5ef76d9c61..45a6e88ccb 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/MzLoader.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/MzLoader.java
@@ -20,7 +20,6 @@ import java.math.BigInteger;
import java.util.*;
import ghidra.app.util.MemoryBlockUtils;
-import ghidra.app.util.Option;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.bin.format.mz.*;
@@ -81,18 +80,21 @@ public class MzLoader extends AbstractLibrarySupportLoader {
}
@Override
- public void load(ByteProvider provider, LoadSpec loadSpec, List options,
- Program program, TaskMonitor monitor, MessageLog log)
+ public void load(Program program, ImporterSettings settings)
throws IOException, CancelledException {
- FileBytes fileBytes = MemoryBlockUtils.createFileBytes(program, provider, monitor);
+ MessageLog log = settings.log();
+ TaskMonitor monitor = settings.monitor();
+
+ FileBytes fileBytes =
+ MemoryBlockUtils.createFileBytes(program, settings.provider(), monitor);
AddressFactory af = program.getAddressFactory();
if (!(af.getDefaultAddressSpace() instanceof SegmentedAddressSpace)) {
throw new IOException("Selected Language must have a segmented address space.");
}
SegmentedAddressSpace space = (SegmentedAddressSpace) af.getDefaultAddressSpace();
- MzExecutable mz = new MzExecutable(provider);
+ MzExecutable mz = new MzExecutable(settings.provider());
try {
Set relocationFixups = getRelocationFixups(space, mz, log, monitor);
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/NeLoader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/NeLoader.java
index a2aebc7b0e..d56545be40 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/NeLoader.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/NeLoader.java
@@ -20,8 +20,9 @@ import java.io.IOException;
import java.math.BigInteger;
import java.util.*;
+import org.apache.commons.io.FilenameUtils;
+
import ghidra.app.util.MemoryBlockUtils;
-import ghidra.app.util.Option;
import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.bin.format.ne.*;
import ghidra.app.util.importer.MessageLog;
@@ -84,8 +85,11 @@ public class NeLoader extends AbstractOrdinalSupportLoader {
}
@Override
- public void load(ByteProvider provider, LoadSpec loadSpec, List options, Program prog,
- TaskMonitor monitor, MessageLog log) throws IOException, CancelledException {
+ public void load(Program prog, ImporterSettings settings)
+ throws IOException, CancelledException {
+
+ MessageLog log = settings.log();
+ TaskMonitor monitor = settings.monitor();
if (monitor.isCancelled()) {
return;
@@ -94,10 +98,11 @@ public class NeLoader extends AbstractOrdinalSupportLoader {
initVars();
- FileBytes fileBytes = MemoryBlockUtils.createFileBytes(prog, provider, monitor);
+ FileBytes fileBytes = MemoryBlockUtils.createFileBytes(prog, settings.provider(), monitor);
SegmentedAddressSpace space =
(SegmentedAddressSpace) prog.getAddressFactory().getDefaultAddressSpace();
- NewExecutable ne = new NewExecutable(provider, space.getAddress(SEGMENT_START, 0));
+ NewExecutable ne =
+ new NewExecutable(settings.provider(), space.getAddress(SEGMENT_START, 0));
WindowsHeader wh = ne.getWindowsHeader();
InformationBlock ib = wh.getInformationBlock();
SegmentTable st = wh.getSegmentTable();
@@ -171,13 +176,9 @@ public class NeLoader extends AbstractOrdinalSupportLoader {
}
@Override
- protected boolean isOptionalLibraryFilenameExtensions() {
- return true;
- }
-
- @Override
- protected boolean isCaseInsensitiveLibraryFilenames() {
- return true;
+ protected Comparator getLibraryNameComparator() {
+ return (s1, s2) -> String.CASE_INSENSITIVE_ORDER.compare(FilenameUtils.getBaseName(s1),
+ FilenameUtils.getBaseName(s2));
}
//////////////////////////////////////////////////////////////////
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/Omf51Loader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/Omf51Loader.java
index 66edcdb1d2..a0658184ee 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/Omf51Loader.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/Omf51Loader.java
@@ -20,7 +20,6 @@ import java.util.*;
import java.util.stream.Collectors;
import ghidra.app.util.MemoryBlockUtils;
-import ghidra.app.util.Option;
import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.bin.StructConverter;
import ghidra.app.util.bin.format.omf.*;
@@ -74,12 +73,13 @@ public class Omf51Loader extends AbstractProgramWrapperLoader {
}
@Override
- protected void load(ByteProvider provider, LoadSpec loadSpec, List options,
- Program program, TaskMonitor monitor, MessageLog log)
+ protected void load(Program program, ImporterSettings settings)
throws IOException, CancelledException {
-
- FileBytes fileBytes = MemoryBlockUtils.createFileBytes(program, provider, monitor);
- AbstractOmfRecordFactory factory = new Omf51RecordFactory(provider);
+ MessageLog log = settings.log();
+ TaskMonitor monitor = settings.monitor();
+ FileBytes fileBytes =
+ MemoryBlockUtils.createFileBytes(program, settings.provider(), monitor);
+ AbstractOmfRecordFactory factory = new Omf51RecordFactory(settings.provider());
try {
List records = OmfUtils.readRecords(factory);
Map segmentToAddr =
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/OmfLoader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/OmfLoader.java
index d8a4c254a6..e40e9faa4f 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/OmfLoader.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/OmfLoader.java
@@ -20,7 +20,6 @@ import java.util.*;
import java.util.stream.Collectors;
import ghidra.app.util.MemoryBlockUtils;
-import ghidra.app.util.Option;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.bin.format.omf.*;
@@ -109,12 +108,12 @@ public class OmfLoader extends AbstractProgramWrapperLoader {
}
@Override
- protected void load(ByteProvider provider, LoadSpec loadSpec, List options,
- Program program, TaskMonitor monitor, MessageLog log)
+ protected void load(Program program, ImporterSettings settings)
throws IOException, CancelledException {
-
+ MessageLog log = settings.log();
+ TaskMonitor monitor = settings.monitor();
OmfFileHeader header = null;
- AbstractOmfRecordFactory factory = new OmfRecordFactory(provider);
+ AbstractOmfRecordFactory factory = new OmfRecordFactory(settings.provider());
try {
header = OmfFileHeader.parse(factory, monitor, log);
header.resolveNames();
@@ -125,13 +124,15 @@ public class OmfLoader extends AbstractProgramWrapperLoader {
if (header == null) {
throw new IOException("OMF File header was corrupted. " + e.getMessage());
}
- log.appendMsg("File was corrupted - leaving partial program " + provider.getName());
+ log.appendMsg(
+ "File was corrupted - leaving partial program " + settings.provider().getName());
}
// We don't use the file bytes to create block because the bytes are manipulated before
// forming the block. Creating the FileBytes anyway in case later we want access to all
// the original bytes.
- FileBytes fileBytes = MemoryBlockUtils.createFileBytes(program, provider, monitor);
+ FileBytes fileBytes =
+ MemoryBlockUtils.createFileBytes(program, settings.provider(), monitor);
try {
processSegmentHeaders(factory.getReader(), header, program, monitor, log);
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/PeLoader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/PeLoader.java
index a0883c05fd..779f97ce88 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/PeLoader.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/PeLoader.java
@@ -19,6 +19,8 @@ import java.io.IOException;
import java.io.InputStream;
import java.util.*;
+import org.apache.commons.io.FilenameUtils;
+
import com.google.common.primitives.Bytes;
import ghidra.app.plugin.core.analysis.rust.RustConstants;
@@ -103,16 +105,18 @@ public class PeLoader extends AbstractPeDebugLoader {
}
@Override
- protected void load(ByteProvider provider, LoadSpec loadSpec, List options,
- Program program, TaskMonitor monitor, MessageLog log)
+ protected void load(Program program, ImporterSettings settings)
throws IOException, CancelledException {
+ MessageLog log = settings.log();
+ TaskMonitor monitor = settings.monitor();
+
if (monitor.isCancelled()) {
return;
}
- PortableExecutable pe = new PortableExecutable(provider, getSectionLayout(), false,
- shouldParseCliHeaders(options));
+ PortableExecutable pe = new PortableExecutable(settings.provider(), getSectionLayout(),
+ false, shouldParseCliHeaders(settings.options()));
NTHeader ntHeader = pe.getNTHeader();
if (ntHeader == null) {
@@ -121,7 +125,7 @@ public class PeLoader extends AbstractPeDebugLoader {
OptionalHeader optionalHeader = ntHeader.getOptionalHeader();
monitor.setMessage("Completing PE header parsing...");
- FileBytes fileBytes = createFileBytes(provider, program, monitor);
+ FileBytes fileBytes = createFileBytes(settings.provider(), program, monitor);
try {
Map sectionToAddress =
processMemoryBlocks(pe, program, fileBytes, monitor, log);
@@ -146,14 +150,16 @@ public class PeLoader extends AbstractPeDebugLoader {
processImports(optionalHeader, program, monitor, log);
processDelayImports(optionalHeader, program, monitor, log);
processRelocations(optionalHeader, program, monitor, log);
- processDebug(optionalHeader, ntHeader, sectionToAddress, program, options, monitor);
+ processDebug(optionalHeader, ntHeader, sectionToAddress, program, settings.options(),
+ monitor);
processProperties(optionalHeader, ntHeader, program, monitor);
processComments(program.getListing(), monitor);
processSymbols(ntHeader, sectionToAddress, program, monitor, log);
processEntryPoints(ntHeader, program, monitor);
String compiler =
- CompilerOpinion.getOpinion(pe, provider, program, monitor, log).toString();
+ CompilerOpinion.getOpinion(pe, settings.provider(), program, monitor, log)
+ .toString();
program.setCompiler(compiler);
}
catch (AddressOverflowException e) {
@@ -183,9 +189,9 @@ public class PeLoader extends AbstractPeDebugLoader {
@Override
public List getDefaultOptions(ByteProvider provider, LoadSpec loadSpec,
- DomainObject domainObject, boolean loadIntoProgram) {
- List list =
- super.getDefaultOptions(provider, loadSpec, domainObject, loadIntoProgram);
+ DomainObject domainObject, boolean loadIntoProgram, boolean mirrorFsLayout) {
+ List list = super.getDefaultOptions(provider, loadSpec, domainObject,
+ loadIntoProgram, mirrorFsLayout);
if (!loadIntoProgram) {
list.add(new Option(PARSE_CLI_HEADERS_OPTION_NAME, PARSE_CLI_HEADERS_OPTION_DEFAULT,
Boolean.class, Loader.COMMAND_LINE_ARG_PREFIX + "-parseCliHeaders"));
@@ -210,8 +216,9 @@ public class PeLoader extends AbstractPeDebugLoader {
}
@Override
- protected boolean isCaseInsensitiveLibraryFilenames() {
- return true;
+ protected Comparator getLibraryNameComparator() {
+ return (s1, s2) -> String.CASE_INSENSITIVE_ORDER.compare(FilenameUtils.getName(s1),
+ FilenameUtils.getName(s2));
}
private boolean shouldParseCliHeaders(List options) {
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/PefLoader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/PefLoader.java
index 649d6eda78..3d274bc7e2 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/PefLoader.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/PefLoader.java
@@ -4,9 +4,9 @@
* 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.
@@ -22,7 +22,6 @@ import java.util.*;
import ghidra.app.cmd.data.CreateDataCmd;
import ghidra.app.cmd.label.AddUniqueLabelCmd;
import ghidra.app.util.MemoryBlockUtils;
-import ghidra.app.util.Option;
import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.bin.format.pef.*;
import ghidra.app.util.importer.MessageLog;
@@ -68,15 +67,17 @@ public class PefLoader extends AbstractProgramWrapperLoader {
}
@Override
- public void load(ByteProvider provider, LoadSpec loadSpec, List options,
- Program program, TaskMonitor monitor, MessageLog log)
+ public void load(Program program, ImporterSettings settings)
throws IOException, CancelledException {
- FileBytes fileBytes = MemoryBlockUtils.createFileBytes(program, provider, monitor);
+ MessageLog log = settings.log();
+ TaskMonitor monitor = settings.monitor();
+ FileBytes fileBytes =
+ MemoryBlockUtils.createFileBytes(program, settings.provider(), monitor);
ImportStateCache importState = null;
try {
- ContainerHeader header = new ContainerHeader(provider);
+ ContainerHeader header = new ContainerHeader(settings.provider());
monitor.setMessage("Completing PEF header parsing...");
monitor.setCancelEnabled(false);
header.parse();
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/UnixAoutLoader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/UnixAoutLoader.java
index 24f6cb80e3..d1ec5e825c 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/UnixAoutLoader.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/UnixAoutLoader.java
@@ -22,13 +22,11 @@ import ghidra.app.util.Option;
import ghidra.app.util.OptionException;
import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.bin.format.unixaout.UnixAoutHeader;
-import ghidra.app.util.importer.MessageLog;
import ghidra.framework.model.DomainObject;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.LanguageCompilerSpecPair;
import ghidra.program.model.listing.Program;
import ghidra.util.exception.CancelledException;
-import ghidra.util.task.TaskMonitor;
/**
* A {@link Loader} for processing UNIX-style A.out executables
@@ -73,15 +71,14 @@ public class UnixAoutLoader extends AbstractProgramWrapperLoader {
}
@Override
- protected void load(ByteProvider provider, LoadSpec loadSpec, List options,
- Program program, TaskMonitor monitor, MessageLog log)
+ protected void load(Program program, ImporterSettings settings)
throws CancelledException, IOException {
final boolean isLittleEndian = !program.getLanguage().isBigEndian();
- final UnixAoutHeader header = new UnixAoutHeader(provider, isLittleEndian);
+ final UnixAoutHeader header = new UnixAoutHeader(settings.provider(), isLittleEndian);
final UnixAoutProgramLoader loader =
- new UnixAoutProgramLoader(program, header, monitor, log);
- loader.loadAout(getBaseAddrOffset(options));
+ new UnixAoutProgramLoader(program, header, settings.monitor(), settings.log());
+ loader.loadAout(getBaseAddrOffset(settings.options()));
}
@Override
@@ -112,7 +109,7 @@ public class UnixAoutLoader extends AbstractProgramWrapperLoader {
@Override
public List getDefaultOptions(ByteProvider provider, LoadSpec loadSpec,
- DomainObject domainObject, boolean loadIntoProgram) {
+ DomainObject domainObject, boolean loadIntoProgram, boolean mirrorFsLayout) {
Address baseAddr = null;
if (domainObject instanceof Program) {
@@ -130,7 +127,8 @@ public class UnixAoutLoader extends AbstractProgramWrapperLoader {
list.add(new Option(OPTION_NAME_BASE_ADDR, baseAddr, Address.class,
Loader.COMMAND_LINE_ARG_PREFIX + "-baseAddr"));
- list.addAll(super.getDefaultOptions(provider, loadSpec, domainObject, loadIntoProgram));
+ list.addAll(super.getDefaultOptions(provider, loadSpec, domainObject, loadIntoProgram,
+ mirrorFsLayout));
return list;
}
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 dce94b88e5..84f949b647 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
@@ -30,7 +30,6 @@ import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.importer.MessageLog;
import ghidra.app.util.xml.*;
import ghidra.framework.model.DomainObject;
-import ghidra.framework.model.Project;
import ghidra.program.model.address.Address;
import ghidra.program.model.lang.*;
import ghidra.program.model.listing.Program;
@@ -177,18 +176,14 @@ public class XmlLoader extends AbstractProgramLoader {
}
@Override
- protected List> loadProgram(ByteProvider provider, String programName,
- Project project, String programFolderPath, LoadSpec loadSpec, List options,
- MessageLog log, Object consumer, TaskMonitor monitor)
+ protected List> loadProgram(ImporterSettings settings)
throws IOException, LoadException, CancelledException {
List> results = new ArrayList<>();
- LanguageCompilerSpecPair pair = loadSpec.getLanguageCompilerSpec();
+ LanguageCompilerSpecPair pair = settings.loadSpec().getLanguageCompilerSpec();
Language importerLanguage = getLanguageService().getLanguage(pair.languageID);
- CompilerSpec importerCompilerSpec =
- importerLanguage.getCompilerSpecByID(pair.compilerSpecID);
- ParseResult result = parse(provider);
+ ParseResult result = parse(settings.provider());
if (result.lastInfo == null) {
return results;
@@ -197,15 +192,14 @@ public class XmlLoader extends AbstractProgramLoader {
if (result.lastInfo.imageBase != null) {
imageBase = importerLanguage.getAddressFactory().getAddress(result.lastInfo.imageBase);
}
- Program prog = createProgram(provider, programName, imageBase, getName(), importerLanguage,
- importerCompilerSpec, consumer);
- List> loadedList =
- List.of(new Loaded<>(prog, programName, project, programFolderPath, consumer));
+ Program prog = createProgram(imageBase, settings);
+ List> loadedList = List.of(new Loaded<>(prog, settings));
boolean success = false;
try {
- success = doImport(result.lastXmlMgr, options, log, prog, monitor, false);
+ success = doImport(result.lastXmlMgr, settings.options(), settings.log(), prog,
+ settings.monitor(), false);
if (success) {
- createDefaultMemoryBlocks(prog, importerLanguage, log);
+ createDefaultMemoryBlocks(prog, settings);
return loadedList;
}
throw new LoadException("Failed to load");
@@ -218,11 +212,11 @@ public class XmlLoader extends AbstractProgramLoader {
}
@Override
- protected void loadProgramInto(ByteProvider provider, LoadSpec loadSpec,
- List options, MessageLog log, Program prog, TaskMonitor monitor)
+ protected void loadProgramInto(Program prog, ImporterSettings settings)
throws IOException, LoadException, CancelledException {
- File file = provider.getFile();
- doImport(new ProgramXmlMgr(file), options, log, prog, monitor, true);
+ File file = settings.provider().getFile();
+ doImport(new ProgramXmlMgr(file), settings.options(), settings.log(), prog,
+ settings.monitor(), true);
}
private boolean doImportWork(final ProgramXmlMgr mgr, final List options,
@@ -325,7 +319,7 @@ public class XmlLoader extends AbstractProgramLoader {
@Override
public List getDefaultOptions(ByteProvider provider, LoadSpec loadSpec,
- DomainObject domainObject, boolean loadIntoProgram) {
+ DomainObject domainObject, boolean loadIntoProgram, boolean mirrorFsLayout) {
return new XmlProgramOptions().getOptions(loadIntoProgram);
}
@@ -335,7 +329,8 @@ public class XmlLoader extends AbstractProgramLoader {
}
@Override
- public String validateOptions(ByteProvider provider, LoadSpec loadSpec, List options, Program program) {
+ public String validateOptions(ByteProvider provider, LoadSpec loadSpec, List options,
+ Program program) {
// XXX will this work? is there other state that xmlOptions needs to
// know?
try {
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/FSUtilities.java b/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/FSUtilities.java
index 95ee68878b..f0b3a8b352 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/FSUtilities.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/FSUtilities.java
@@ -651,4 +651,34 @@ public class FSUtilities {
return FileType.UNKNOWN;
}
+ /**
+ * Splits the given path into its individual directory and filename components. For example,
+ * {@code "/dir/dir/dir/file"} becomes {@code ["", "dir", "dir", "dir", "file"]}.
+ *
+ * @param path The path to split
+ * @return The split path
+ */
+ public static String[] splitPath(String path) {
+ return Objects.requireNonNullElse(path, "").replace('\\', '/').split("/");
+ }
+
+ /**
+ * Converts the given path to a valid mirrored project path. Care must be taken to minimize
+ * project path collisions, allowing them only when completely necessary.
+ *
+ * @param path The path to convert
+ * @return The mirrored project path
+ */
+ public static String mirroredProjectPath(String path) {
+ path = FSUtilities.normalizeNativePath(path);
+
+ // If it looks like an absolute Windows path, drop the colon from the drive letter. The
+ // colon is not a valid project character.
+ if (path.length() >= 3 && path.charAt(0) == '/' && Character.isLetter(path.charAt(1)) &&
+ path.charAt(2) == ':') {
+ path = "/" + path.charAt(1) + path.substring(3);
+ }
+ return path;
+ }
+
}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/FileSystemIndexHelper.java b/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/FileSystemIndexHelper.java
index 13572a96e9..aa08ce0aca 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/FileSystemIndexHelper.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/FileSystemIndexHelper.java
@@ -179,7 +179,7 @@ public class FileSystemIndexHelper {
try {
FileData baseDirData = getFileData(baseDir);
FileData fileData =
- lookup(baseDirData, splitPath(path), -1, false, nameComp);
+ lookup(baseDirData, FSUtilities.splitPath(path), -1, false, nameComp);
return (fileData != null) ? fileData.file : null;
}
catch (IOException e) {
@@ -222,7 +222,7 @@ public class FileSystemIndexHelper {
symlinkPathDebug.append("[");
FileData currentFile = Objects.requireNonNullElse(baseDir, rootDir);
- String[] pathparts = splitPath(path);
+ String[] pathparts = FSUtilities.splitPath(path);
for (int i = 0; i < pathparts.length && currentFile != null; i++) {
String name = pathparts[i];
symlinkPathDebug.append(i != 0 ? "," : "").append(name);
@@ -309,7 +309,7 @@ public class FileSystemIndexHelper {
public synchronized GFile storeFile(String path, long fileIndex, boolean isDirectory,
long length, METADATATYPE metadata) {
- String[] nameparts = splitPath(path);
+ String[] nameparts = FSUtilities.splitPath(path);
if (nameparts.length == 0) {
return rootDir.file;
}
@@ -369,7 +369,7 @@ public class FileSystemIndexHelper {
*/
public synchronized GFile storeSymlink(String path, long fileIndex, String symlinkPath,
long length, METADATATYPE metadata) {
- String[] nameparts = splitPath(path);
+ String[] nameparts = FSUtilities.splitPath(path);
if (nameparts.length == 0) {
Msg.warn(this,
"Unable to create invalid symlink file [%s] -> [%s]".formatted(path, symlinkPath));
@@ -498,10 +498,6 @@ public class FileSystemIndexHelper {
return parent.file;
}
- protected String[] splitPath(String path) {
- return Objects.requireNonNullElse(path, "").replace('\\', '/').split("/");
- }
-
protected FileData lookupFileInDir(
Map> dirContents, String filename,
Comparator nameComp) {
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/FileSystemRef.java b/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/FileSystemRef.java
index 0e84bd0e42..5cb02eb34c 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/FileSystemRef.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/FileSystemRef.java
@@ -80,4 +80,9 @@ public class FileSystemRef implements Closeable {
Msg.warn(this, "Unclosed FilesytemRef: " + fs.toString());
}
}
+
+ @Override
+ public String toString() {
+ return fs.getFSRL().toString();
+ }
}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/plugin/importer/AddToProgramDialog.java b/Ghidra/Features/Base/src/main/java/ghidra/plugin/importer/AddToProgramDialog.java
index bb256e0a9b..5deaf0fccb 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/plugin/importer/AddToProgramDialog.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/plugin/importer/AddToProgramDialog.java
@@ -4,9 +4,9 @@
* 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.
@@ -61,6 +61,7 @@ public class AddToProgramDialog extends ImporterDialog {
folderButton.setEnabled(false);
languageButton.setEnabled(false);
nameTextField.setEnabled(false);
+ mirrorFsCheckBox.setVisible(false);
validateFormInput();
}
@@ -78,8 +79,8 @@ public class AddToProgramDialog extends ImporterDialog {
LoadSpec loadSpec = getSelectedLoadSpec(loader);
- String result =
- loader.validateOptions(byteProvider, loadSpec, getOptions(loadSpec), addToProgram);
+ String result = loader.validateOptions(byteProvider, loadSpec, getOptions(loadSpec, false),
+ addToProgram);
if (result != null) {
setStatusText(result);
@@ -103,7 +104,8 @@ public class AddToProgramDialog extends ImporterDialog {
LoadSpec selectedLoadSpec = getSelectedLoadSpec(selectedLoader);
if (options == null) {
- options = selectedLoader.getDefaultOptions(byteProvider, selectedLoadSpec, null, true);
+ options =
+ selectedLoader.getDefaultOptions(byteProvider, selectedLoadSpec, null, true, false);
}
TaskLauncher.launchNonModal("Import File", monitor -> {
ImporterUtilities.addContentToProgram(tool, addToProgram, fsrl, selectedLoadSpec,
@@ -113,11 +115,12 @@ public class AddToProgramDialog extends ImporterDialog {
}
@Override
- protected List getOptions(LoadSpec loadSpec) {
- if (options != null) {
+ protected List getOptions(LoadSpec loadSpec, boolean forceRefresh) {
+ if (options != null && !forceRefresh) {
return options;
}
- return loadSpec.getLoader().getDefaultOptions(byteProvider, loadSpec, addToProgram, true);
+ return loadSpec.getLoader()
+ .getDefaultOptions(byteProvider, loadSpec, addToProgram, true, false);
}
/**
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/plugin/importer/ImporterDialog.java b/Ghidra/Features/Base/src/main/java/ghidra/plugin/importer/ImporterDialog.java
index 5bd5a9d72b..3760ebf483 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/plugin/importer/ImporterDialog.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/plugin/importer/ImporterDialog.java
@@ -55,9 +55,9 @@ import ghidra.framework.store.local.LocalFileSystem;
import ghidra.program.model.address.AddressFactory;
import ghidra.program.model.lang.LanguageCompilerSpecPair;
import ghidra.program.model.lang.LanguageNotFoundException;
-import ghidra.util.*;
-import ghidra.util.layout.PairLayout;
-import ghidra.util.layout.VerticalLayout;
+import ghidra.util.HelpLocation;
+import ghidra.util.Msg;
+import ghidra.util.layout.*;
import ghidra.util.task.TaskBuilder;
/**
@@ -74,6 +74,7 @@ public class ImporterDialog extends DialogComponentProvider {
private DomainFolder destinationFolder;
private boolean languageNeeded;
private String suggestedDestinationPath;
+ private String previousName;
protected ByteProvider byteProvider;
protected JTextField nameTextField;
@@ -81,6 +82,7 @@ public class ImporterDialog extends DialogComponentProvider {
protected JButton folderButton;
protected JButton languageButton;
protected JTextField languageTextField;
+ protected JCheckBox mirrorFsCheckBox;
protected JButton optionsButton;
protected JTextField folderNameTextField;
protected GhidraComboBox loaderComboBox;
@@ -322,13 +324,21 @@ public class ImporterDialog extends DialogComponentProvider {
private Component buildButtonPanel() {
JPanel panel = new JPanel(new BorderLayout());
- JPanel innerPanel = new JPanel(new VerticalLayout(5));
+ JPanel innerPanel = new JPanel(new HorizontalLayout(5));
+ innerPanel.add(buildMirrorFsCheckbox());
innerPanel.add(buildOptionsButton());
panel.add(innerPanel, BorderLayout.EAST);
panel.getAccessibleContext().setAccessibleName("Buttons");
return panel;
}
+ private Component buildMirrorFsCheckbox() {
+ mirrorFsCheckBox = new JCheckBox("Mirror Filesystem", false);
+ mirrorFsCheckBox.addActionListener(e -> mirrorFs());
+ mirrorFsCheckBox.getAccessibleContext().setAccessibleName("Mirror");
+ return mirrorFsCheckBox;
+ }
+
private Component buildOptionsButton() {
optionsButton = new JButton("Options...");
optionsButton.addActionListener(e -> showOptions());
@@ -338,25 +348,25 @@ public class ImporterDialog extends DialogComponentProvider {
@Override
protected void okCallback() {
- if (validateFormInput()) {
- Loader loader = getSelectedLoader();
- LoadSpec loadSpec = getSelectedLoadSpec(loader);
- String programPath = removeTrailingSlashes(getName());
- DomainFolder importFolder = getOrCreateImportFolder(destinationFolder, programPath);
- String programName = FilenameUtils.getName(programPath);
- options = getOptions(loadSpec); // make sure you get the options now, before the ByteProvider is closed.
-
- //@formatter:off
- new TaskBuilder("Import File", monitor -> {
- ImporterUtilities.importSingleFile(tool, programManager, fsrl, importFolder,
- loadSpec, programName, options, monitor);
- })
- .setLaunchDelay(0)
- .launchNonModal();
- //@formatter:on
-
- close();
+ if (!validateFormInput()) {
+ return;
}
+
+ Loader loader = getSelectedLoader();
+ LoadSpec loadSpec = getSelectedLoadSpec(loader);
+ options = getOptions(loadSpec, false); // make sure you get the options now, before the ByteProvider is closed.
+
+ //@formatter:off
+ new TaskBuilder("Import File", monitor -> {
+ ImporterUtilities.importSingleFile(tool, programManager, fsrl,
+ destinationFolder.getPathname(), mirrorFsCheckBox.isSelected(), loadSpec,
+ removeTrailingSlashes(getName()), options, monitor);
+ })
+ .setLaunchDelay(0)
+ .launchNonModal();
+ //@formatter:on
+
+ close();
}
private String removeTrailingSlashes(String path) {
@@ -366,24 +376,6 @@ public class ImporterDialog extends DialogComponentProvider {
return path;
}
- private DomainFolder getOrCreateImportFolder(DomainFolder parentFolder, String programPath) {
- int lastIndexOf = programPath.lastIndexOf("/");
- if (lastIndexOf < 0) {
- return parentFolder;
- }
- String folderPath = programPath.substring(0, lastIndexOf);
- try {
- return ProjectDataUtils.createDomainFolderPath(parentFolder, folderPath);
- }
- catch (InvalidNameException e) {
- Msg.showError(this, null, "Error Creating Folders", e.getMessage());
- }
- catch (IOException e) {
- Msg.showError(this, null, "Error Creating Folders", "I/O Error" + e.getMessage(), e);
- }
- return parentFolder;
- }
-
@Override
public void close() {
super.close();
@@ -395,13 +387,29 @@ public class ImporterDialog extends DialogComponentProvider {
}
}
- protected List getOptions(LoadSpec loadSpec) {
- if (options == null) {
- options = loadSpec.getLoader().getDefaultOptions(byteProvider, loadSpec, null, false);
+ protected List getOptions(LoadSpec loadSpec, boolean forceRefresh) {
+ if (options == null || forceRefresh) {
+ options = loadSpec.getLoader()
+ .getDefaultOptions(byteProvider, loadSpec, null, false,
+ mirrorFsCheckBox.isSelected());
}
return options;
}
+ private void mirrorFs() {
+ nameTextField.setEnabled(!mirrorFsCheckBox.isSelected());
+ if (mirrorFsCheckBox.isSelected()) {
+ previousName = getName();
+ String path = FSUtilities.mirroredProjectPath(fsrl.getPath());
+ nameTextField.setText(path);
+ nameTextField.setCaretPosition(path.length());
+ }
+ else if (previousName != null) {
+ nameTextField.setText(previousName);
+ nameTextField.setCaretPosition(previousName.length());
+ }
+ }
+
private void showOptions() {
try {
Loader loader = getSelectedLoader();
@@ -412,7 +420,7 @@ public class ImporterDialog extends DialogComponentProvider {
AddressFactoryService service = () -> addressFactory;
- List currentOptions = getOptions(loadSpec);
+ List currentOptions = getOptions(loadSpec, true);
if (currentOptions.isEmpty()) {
Msg.showInfo(this, null, "Options", "There are no options for this importer!");
return;
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/plugin/importer/ImporterPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/plugin/importer/ImporterPlugin.java
index 63ba37eea8..a6ce4e3d4f 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/plugin/importer/ImporterPlugin.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/plugin/importer/ImporterPlugin.java
@@ -42,7 +42,8 @@ import ghidra.formats.gfilesystem.FileCache.FileCacheEntry;
import ghidra.formats.gfilesystem.FileCache.FileCacheEntryBuilder;
import ghidra.formats.gfilesystem.FileSystemService;
import ghidra.framework.main.*;
-import ghidra.framework.main.datatree.*;
+import ghidra.framework.main.datatree.DataTree;
+import ghidra.framework.main.datatree.JavaFileListHandler;
import ghidra.framework.model.*;
import ghidra.framework.options.ToolOptions;
import ghidra.framework.plugintool.*;
@@ -169,7 +170,7 @@ public class ImporterPlugin extends Plugin
return false;
}
return loadSpec.getLoader()
- .getDefaultOptions(provider, loadSpec, null, false)
+ .getDefaultOptions(provider, loadSpec, null, false, false)
.stream()
.anyMatch(e -> e.getName()
.equals(AbstractLibrarySupportLoader.LOAD_ONLY_LIBRARIES_OPTION_NAME));
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/plugin/importer/ImporterUtilities.java b/Ghidra/Features/Base/src/main/java/ghidra/plugin/importer/ImporterUtilities.java
index 3c209e398b..581108613b 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/plugin/importer/ImporterUtilities.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/plugin/importer/ImporterUtilities.java
@@ -28,6 +28,7 @@ import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.bin.FileBytesProvider;
import ghidra.app.util.importer.MessageLog;
import ghidra.app.util.opinion.*;
+import ghidra.app.util.opinion.Loader.ImporterSettings;
import ghidra.formats.gfilesystem.*;
import ghidra.framework.main.AppInfo;
import ghidra.framework.main.FrontEndTool;
@@ -276,7 +277,7 @@ public class ImporterUtilities {
}
LoadSpec loadSpec = getLoadSpec(provider, program);
if (loadSpec == null || loadSpec.getLoader()
- .getDefaultOptions(provider, loadSpec, null, false)
+ .getDefaultOptions(provider, loadSpec, null, false, false)
.stream()
.noneMatch(e -> e.getName()
.equals(
@@ -354,9 +355,10 @@ public class ImporterUtilities {
if (program == null) {
return;
}
- try (LoadResults extends DomainObject> loadResults = new LoadResults<>(program,
- program.getName(), tool.getProject(), destFolder.getPathname(), consumer)) {
- doPostImportProcessing(tool, programManager, fsrl, loadResults, "", monitor);
+ try (LoadResults extends DomainObject> loadResults =
+ new LoadResults<>(new Loaded<>(program, program.getName(), fsrl,
+ tool.getProject(), destFolder.getPathname(), false, consumer))) {
+ doPostImportProcessing(tool, programManager, loadResults, "", monitor);
}
}
catch (Exception e) {
@@ -402,28 +404,34 @@ public class ImporterUtilities {
* @param tool tool to which popup dialogs should be associated
* @param programManager program manager to open imported file with or null
* @param fsrl import file location
- * @param destFolder project destination folder
+ * @param projectRootPath The project folder path that all imported things will be saved
+ * relative to. If {@code null}, "/" will be used.
+ * @param mirrorFsLayout True if the filesystem layout should be mirrored when loading;
+ * otherwise, false
* @param loadSpec import {@link LoadSpec}
- * @param programName program name
+ * @param importName The import name. Path information that appears at the beginning the name
+ * will be appended to the {@code projectRootPath} during the saving process.
* @param options import options
* @param monitor task monitor
*/
public static void importSingleFile(PluginTool tool, ProgramManager programManager, FSRL fsrl,
- DomainFolder destFolder, LoadSpec loadSpec, String programName, List options,
- TaskMonitor monitor) {
+ String projectRootPath, boolean mirrorFsLayout, LoadSpec loadSpec, String importName,
+ List options, TaskMonitor monitor) {
Objects.requireNonNull(monitor);
try (ByteProvider bp = fsService.getByteProvider(fsrl, false, monitor)) {
-
- Object consumer = new Object();
MessageLog messageLog = new MessageLog();
- try (LoadResults extends DomainObject> loadResults = loadSpec.getLoader()
- .load(bp, programName, tool.getProject(), destFolder.getPathname(), loadSpec,
- options, messageLog, consumer, monitor)) {
+
+ ImporterSettings settings = new ImporterSettings(bp, importName, tool.getProject(),
+ projectRootPath, mirrorFsLayout, loadSpec, options, new Object(), messageLog,
+ monitor);
+
+ try (LoadResults extends DomainObject> loadResults =
+ loadSpec.getLoader().load(settings)) {
loadResults.save(monitor);
- doPostImportProcessing(tool, programManager, fsrl, loadResults,
- messageLog.toString(), monitor);
+ doPostImportProcessing(tool, programManager, loadResults, messageLog.toString(),
+ monitor);
}
}
@@ -432,14 +440,13 @@ public class ImporterUtilities {
}
catch (Exception e) {
Msg.showError(ImporterUtilities.class, tool.getActiveWindow(), "Error Importing File",
- "Error importing file: " + fsrl.getName(), e);
+ "Error importing file: %s (%s)".formatted(fsrl.getName(), e.getMessage()));
}
}
private static Set doPostImportProcessing(PluginTool pluginTool,
- ProgramManager programManager, FSRL fsrl,
- LoadResults extends DomainObject> loadResults, String importMessages,
- TaskMonitor monitor) throws CancelledException {
+ ProgramManager programManager, LoadResults extends DomainObject> loadResults,
+ String importMessages, TaskMonitor monitor) throws CancelledException {
boolean firstProgram = true;
Set importedFilesSet = new HashSet<>();
@@ -490,8 +497,13 @@ public class ImporterUtilities {
Objects.requireNonNull(monitor);
MessageLog messageLog = new MessageLog();
+ Object consumer = new Object();
+ program.addConsumer(consumer);
try (ByteProvider bp = fsService.getByteProvider(fsrl, false, monitor)) {
- loadSpec.getLoader().loadInto(bp, loadSpec, options, messageLog, program, monitor);
+ ImporterSettings settings = new ImporterSettings(bp, bp.getName(), tool.getProject(),
+ program.getDomainFile().getPathname(), false, loadSpec, options, consumer,
+ messageLog, monitor);
+ loadSpec.getLoader().loadInto(program, settings);
displayResults(tool, program, program.getDomainFile(), messageLog.toString());
// Optionally echo loader message log to application.log
@@ -506,6 +518,9 @@ public class ImporterUtilities {
Msg.showError(ImporterUtilities.class, null, "Error Importing File",
"Error importing file " + fsrl.getName(), e);
}
+ finally {
+ program.release(consumer);
+ }
}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/plugin/importer/LoadLibrariesOptionsDialog.java b/Ghidra/Features/Base/src/main/java/ghidra/plugin/importer/LoadLibrariesOptionsDialog.java
index 9a83715afd..36dd9b22ad 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/plugin/importer/LoadLibrariesOptionsDialog.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/plugin/importer/LoadLibrariesOptionsDialog.java
@@ -25,6 +25,7 @@ import ghidra.app.util.*;
import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.importer.MessageLog;
import ghidra.app.util.opinion.*;
+import ghidra.app.util.opinion.Loader.ImporterSettings;
import ghidra.framework.model.DomainObject;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.listing.Program;
@@ -70,16 +71,18 @@ public class LoadLibrariesOptionsDialog extends OptionsDialog {
TaskLauncher.launchNonModal(TITLE, monitor -> {
super.okCallback();
Object consumer = new Object();
- MessageLog messageLog = new MessageLog();
- try (LoadResults extends DomainObject> loadResults = loadSpec.getLoader()
- .load(provider, program.getDomainFile().getName(), tool.getProject(),
- program.getDomainFile().getParent().getPathname(), loadSpec,
- getOptions(), messageLog, consumer, monitor)) {
+ MessageLog log = new MessageLog();
+ ImporterSettings settings =
+ new ImporterSettings(provider, program.getDomainFile().getName(), tool.getProject(),
+ program.getDomainFile().getParent().getPathname(), false, loadSpec,
+ getOptions(), consumer, log, monitor);
+ try (LoadResults extends DomainObject> loadResults =
+ loadSpec.getLoader().load(settings)) {
loadResults.save(monitor);
// Display results
- String importMessages = messageLog.toString();
+ String importMessages = log.toString();
if (!importMessages.isEmpty()) {
if (!Loader.loggingDisabled) {
Msg.info(ImporterUtilities.class, TITLE + ":\n" + importMessages);
@@ -112,7 +115,7 @@ public class LoadLibrariesOptionsDialog extends OptionsDialog {
private static List getLoadLibraryOptions(ByteProvider provider, LoadSpec loadSpec) {
List options = new ArrayList<>();
for (Option option : loadSpec.getLoader()
- .getDefaultOptions(provider, loadSpec, null, false)) {
+ .getDefaultOptions(provider, loadSpec, null, false, false)) {
switch (option.getName()) {
case LOAD_ONLY_LIBRARIES_OPTION_NAME:
case LOAD_LIBRARY_OPTION_NAME:
@@ -121,6 +124,7 @@ public class LoadLibrariesOptionsDialog extends OptionsDialog {
case LINK_SEARCH_FOLDER_OPTION_NAME:
case LIBRARY_SEARCH_PATH_DUMMY_OPTION_NAME:
case LIBRARY_DEST_FOLDER_OPTION_NAME:
+ case MIRROR_LAYOUT_OPTION_NAME:
options.add(option);
break;
default:
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/plugins/importer/batch/BatchImportDialog.java b/Ghidra/Features/Base/src/main/java/ghidra/plugins/importer/batch/BatchImportDialog.java
index ec97ed76bc..0a5e803511 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/plugins/importer/batch/BatchImportDialog.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/plugins/importer/batch/BatchImportDialog.java
@@ -57,6 +57,7 @@ public class BatchImportDialog extends DialogComponentProvider {
private static final String PREF_STRIPCONTAINER = "BATCHIMPORT.STRIPCONTAINER";
private static final String PREF_STRIPLEADING = "BATCHIMPORT.STRIPLEADING";
+ private static final String PREF_MIRRORFS = "BATCHIMPORT.MIRRORFS";
private static final String LAST_IMPORT_DIR = "LastBatchImportDir";
/**
@@ -92,8 +93,13 @@ public class BatchImportDialog extends DialogComponentProvider {
private ProgramManager programManager;
private boolean stripLeading = getBooleanPref(PREF_STRIPLEADING, true);
private boolean stripContainer = getBooleanPref(PREF_STRIPCONTAINER, false);
+ private boolean mirrorFs = getBooleanPref(PREF_MIRRORFS, false);
private boolean openAfterImporting = false;
+ private GCheckBox stripLeadingCb;
+ private GCheckBox stripContainerCb;
+ private GCheckBox mirrorFsCb;
+
private BatchImportTableModel tableModel;
private GTable table;
private JButton removeSourceButton;
@@ -315,18 +321,25 @@ public class BatchImportDialog extends DialogComponentProvider {
outputChoicesPanel.setLayout(new BoxLayout(outputChoicesPanel, BoxLayout.LINE_AXIS));
outputChoicesPanel.getAccessibleContext().setAccessibleName("Output Choices");
- GCheckBox stripLeadingCb = new GCheckBox("Strip leading path", stripLeading);
+ stripLeadingCb = new GCheckBox("Strip leading path", stripLeading);
stripLeadingCb.addChangeListener(e -> setStripLeading(stripLeadingCb.isSelected()));
stripLeadingCb.setToolTipText("The destination folder for imported files will not " +
"include the source file's leading path");
stripLeadingCb.getAccessibleContext().setAccessibleName("Strip Leading Path");
- GCheckBox stripContainerCb = new GCheckBox("Strip container paths", stripContainer);
+ stripContainerCb = new GCheckBox("Strip container paths", stripContainer);
stripContainerCb.addChangeListener(e -> setStripContainer(stripContainerCb.isSelected()));
stripContainerCb.setToolTipText(
"The destination folder for imported files will not include any source path names");
stripContainerCb.getAccessibleContext().setAccessibleName("Strip Container Paths");
+ mirrorFsCb = new GCheckBox("Mirror Filesystem", mirrorFs);
+ mirrorFsCb.addChangeListener(e -> setMirrorFs(mirrorFsCb.isSelected()));
+ mirrorFsCb.setToolTipText(
+ "The imported files' project paths will mirror the filesystem rooted at the desination folder");
+ mirrorFsCb.getAccessibleContext().setAccessibleName("Mirror Filesystem");
+ setMirrorFs(mirrorFs); // needed to possibly disable other checkboxes
+
GCheckBox openAfterImportCb = new GCheckBox("Open after import", openAfterImporting);
openAfterImportCb
.addChangeListener(e -> setOpenAfterImporting(openAfterImportCb.isSelected()));
@@ -335,6 +348,7 @@ public class BatchImportDialog extends DialogComponentProvider {
outputChoicesPanel.add(stripLeadingCb);
outputChoicesPanel.add(stripContainerCb);
+ outputChoicesPanel.add(mirrorFsCb);
if (programManager != null) {
outputChoicesPanel.add(openAfterImportCb);
}
@@ -464,7 +478,7 @@ public class BatchImportDialog extends DialogComponentProvider {
protected void okCallback() {
new TaskLauncher(
new ImportBatchTask(batchInfo, destinationFolder,
- openAfterImporting ? programManager : null, stripLeading, stripContainer),
+ openAfterImporting ? programManager : null, stripLeading, stripContainer, mirrorFs),
getComponent());
close();
}
@@ -616,6 +630,13 @@ public class BatchImportDialog extends DialogComponentProvider {
setBooleanPref(PREF_STRIPCONTAINER, stripContainer);
}
+ private void setMirrorFs(boolean mirrorFs) {
+ this.mirrorFs = mirrorFs;
+ setBooleanPref(PREF_MIRRORFS, mirrorFs);
+ stripContainerCb.setEnabled(!mirrorFs);
+ stripContainerCb.setSelected(mirrorFs ? false : stripContainer);
+ }
+
private void setMaxDepth(int newMaxDepth) {
if (newMaxDepth == batchInfo.getMaxDepth()) {
return;
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/plugins/importer/tasks/ImportBatchTask.java b/Ghidra/Features/Base/src/main/java/ghidra/plugins/importer/tasks/ImportBatchTask.java
index e48c566fa2..90e1e94131 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/plugins/importer/tasks/ImportBatchTask.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/plugins/importer/tasks/ImportBatchTask.java
@@ -26,6 +26,7 @@ import ghidra.app.util.Option;
import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.importer.MessageLog;
import ghidra.app.util.opinion.*;
+import ghidra.app.util.opinion.Loader.ImporterSettings;
import ghidra.formats.gfilesystem.*;
import ghidra.framework.main.AppInfo;
import ghidra.framework.model.*;
@@ -54,6 +55,7 @@ public class ImportBatchTask extends Task {
private DomainFolder destFolder;
private boolean stripLeadingPath = true;
private boolean stripAllContainerPath = false;
+ private boolean mirrorFs = false;
private ProgramManager programManager;
private int totalObjsImported;
private int totalAppsImported;
@@ -71,9 +73,11 @@ public class ImportBatchTask extends Task {
* @param stripAllContainerPath boolean true if each imported file's parent container
* source path should be completely omitted when creating the destination project folder path.
* (the imported file's path within its container is still used)
+ * @param mirrorFs boolean true if the filesystem should be mirrored during import
*/
public ImportBatchTask(BatchInfo batchInfo, DomainFolder destFolder,
- ProgramManager programManager, boolean stripLeading, boolean stripAllContainerPath) {
+ ProgramManager programManager, boolean stripLeading, boolean stripAllContainerPath,
+ boolean mirrorFs) {
super("Batch Import Task", true, true, false, false);
this.batchInfo = batchInfo;
@@ -82,6 +86,7 @@ public class ImportBatchTask extends Task {
this.programManager = programManager;
this.stripLeadingPath = stripLeading;
this.stripAllContainerPath = stripAllContainerPath;
+ this.mirrorFs = mirrorFs;
}
@Override
@@ -146,11 +151,12 @@ public class ImportBatchTask extends Task {
try {
MessageLog messageLog = new MessageLog();
Project project = AppInfo.getActiveProject();
- try (LoadResults extends DomainObject> loadResults = loadSpec.getLoader()
- .load(byteProvider, fixupProjectFilename(destInfo.second), project,
- destInfo.first.getPathname(), loadSpec,
- getOptionsFor(batchLoadConfig, loadSpec, byteProvider), messageLog,
- this, monitor)) {
+ ImporterSettings settings = new ImporterSettings(byteProvider,
+ fixupProjectFilename(destInfo.second), project, destInfo.first.getPathname(),
+ mirrorFs, loadSpec, getOptionsFor(batchLoadConfig, loadSpec, byteProvider),
+ this, messageLog, monitor);
+ try (LoadResults extends DomainObject> loadResults =
+ loadSpec.getLoader().load(settings)) {
// TODO: accumulate batch results
if (loadResults != null) {
@@ -170,7 +176,8 @@ public class ImportBatchTask extends Task {
catch (CancelledException e) {
Msg.debug(this, "Batch Import cancelled");
}
- catch (IOException | VersionException | IllegalArgumentException e) {
+ catch (IOException | VersionException | IllegalArgumentException
+ | InvalidNameException e) {
Msg.error(this, "Import failed for " + batchLoadConfig.getPreferredFileName(), e);
}
}
@@ -234,13 +241,12 @@ public class ImportBatchTask extends Task {
String userSrcPath = userSrc.toPrettyFullpathString().replace('|', '/');
int filename = fullPath.lastIndexOf('/') + 1;
int uas = userSrcPath.length();
- int container = uas + 1;
int leadStart = (stripLeadingPath == false) ? 0 : userSrcPath.lastIndexOf('/') + 1;
int leadEnd = Math.min(filename, userSrcPath.length());
String leading = (leadStart < filename) ? fullPath.substring(leadStart, leadEnd) : "";
- String containerPath = container < filename && !stripInteriorContainerPath
- ? fullPath.substring(container, filename)
+ String containerPath = uas < filename && !stripInteriorContainerPath
+ ? fullPath.substring(uas, filename)
: "";
String filenameStr = fullPath.substring(filename);
String result = FSUtilities.appendPath(leading, containerPath, filenameStr);
@@ -250,6 +256,23 @@ public class ImportBatchTask extends Task {
private Pair getDestinationInfo(BatchLoadConfig batchLoadConfig,
DomainFolder rootDestinationFolder) {
FSRL fsrl = batchLoadConfig.getFSRL();
+
+ if (mirrorFs) {
+ FSRL containerFSRL = fsrl.getFS().getContainer();
+ String path = containerFSRL != null ? containerFSRL.toPrettyFullpathString() : "/";
+ path = path.replace('|', '/');
+ try {
+ DomainFolder batchDestFolder = ProjectDataUtils.createDomainFolderPath(
+ rootDestinationFolder, stripLeadingPath ? "" : path);
+ return new Pair<>(batchDestFolder, fsrl.getName());
+ }
+ catch (IOException | InvalidNameException e) {
+ Msg.error(this, "Problem creating project folder root: " +
+ rootDestinationFolder.getPathname() + ", subpath: " + path, e);
+ }
+ return new Pair<>(rootDestinationFolder, fsrl.getName());
+ }
+
String pathStr = fsrlToPath(fsrl, batchLoadConfig.getUasi().getFSRL(), stripLeadingPath,
stripAllContainerPath);
String preferredName = batchLoadConfig.getPreferredFileName();
@@ -280,8 +303,8 @@ public class ImportBatchTask extends Task {
private List getOptionsFor(BatchLoadConfig batchLoadConfig, LoadSpec loadSpec,
ByteProvider byteProvider) {
- List options =
- batchLoadConfig.getLoader().getDefaultOptions(byteProvider, loadSpec, null, false);
+ List options = batchLoadConfig.getLoader()
+ .getDefaultOptions(byteProvider, loadSpec, null, false, false);
return options;
}
}
diff --git a/Ghidra/Features/Base/src/test/java/ghidra/app/util/opinion/DecompileDebugXmlLoaderTest.java b/Ghidra/Features/Base/src/test/java/ghidra/app/util/opinion/DecompileDebugXmlLoaderTest.java
index 9f8ebb6409..6a9290b0e7 100644
--- a/Ghidra/Features/Base/src/test/java/ghidra/app/util/opinion/DecompileDebugXmlLoaderTest.java
+++ b/Ghidra/Features/Base/src/test/java/ghidra/app/util/opinion/DecompileDebugXmlLoaderTest.java
@@ -21,207 +21,137 @@ package ghidra.app.util.opinion;
import static org.junit.Assert.*;
import java.io.File;
-import java.io.IOException;
import java.math.BigInteger;
-import java.nio.file.AccessMode;
-import java.util.*;
+import java.util.Iterator;
import org.junit.Before;
import org.junit.Test;
-import org.xml.sax.SAXException;
-import ghidra.app.util.Option;
-import ghidra.app.util.bin.ByteProvider;
-import ghidra.app.util.bin.FileByteProvider;
-import ghidra.app.util.importer.MessageLog;
+import ghidra.app.util.importer.ProgramLoader;
import ghidra.app.util.opinion.DecompileDebugXmlLoader.DecompileDebugProgramInfo;
-import ghidra.framework.model.DomainObject;
-import ghidra.program.database.ProgramDB;
-import ghidra.program.database.data.ProgramDataTypeManager;
-import ghidra.program.database.function.FunctionManagerDB;
import ghidra.program.model.address.Address;
-import ghidra.program.model.data.DataTypeComponent;
-import ghidra.program.model.data.Structure;
-import ghidra.program.model.listing.Function;
+import ghidra.program.model.data.*;
+import ghidra.program.model.listing.*;
import ghidra.program.model.mem.Memory;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
-import ghidra.util.exception.CancelledException;
-import ghidra.util.exception.VersionException;
-import ghidra.util.task.TaskMonitor;
import resources.ResourceManager;
/**
*
*/
public class DecompileDebugXmlLoaderTest extends AbstractGhidraHeadedIntegrationTest {
- File decompileDebugTestFile;
- private DecompileDebugXmlLoader loader;
- private ProgramDB program;
+ private File decompileDebugTestFile;
+ private ProgramLoader.Builder programLoader;
@Before
public void setUp() {
String DECOMPILE_DEBUG_TEST_FILE = "ghidra/app/util/opinion/decompile_debug_test.xml";
decompileDebugTestFile = ResourceManager.getResourceFile(DECOMPILE_DEBUG_TEST_FILE);
- loader = new DecompileDebugXmlLoader();
+ programLoader = ProgramLoader.builder()
+ .source(decompileDebugTestFile)
+ .loaders(DecompileDebugXmlLoader.class);
}
/**
* Test method for {@link ghidra.app.util.opinion.DecompileDebugFormatManager#getProgramInfo()}.
+ *
+ * @throws Exception if an error occurred
*/
@Test
- public void testDecompileDebugXmlLoad() {
- Collection loadSpecs;
- MessageLog log = new MessageLog();
- try {
- ByteProvider byteProvider =
- new FileByteProvider(decompileDebugTestFile, null, AccessMode.READ);
- loadSpecs = loader.findSupportedLoadSpecs(byteProvider);
- assertTrue("Expected single loader opinion", loadSpecs.size() == 1);
-
- LoadSpec loadSpec = loadSpecs.iterator().next();
- List options = loader.getDefaultOptions(byteProvider, loadSpec, null, false);
-
- LoadResults extends DomainObject> loadResults =
- loader.load(byteProvider, byteProvider.getName(), null, null, loadSpec, options,
- log, this, TaskMonitor.DUMMY);
- loadResults.getNonPrimary();
- program = (ProgramDB) loadResults.getPrimaryDomainObject(this);
+ public void testDecompileDebugXmlLoad() throws Exception {
+ try (LoadResults loadResults = programLoader.load()) {
assertTrue("expected single loaded program", loadResults.size() == 1);
}
- catch (IOException | CancelledException | VersionException e) {
- log.appendException(e);
- }
}
@Test
- public void testVerifyProgramInfo() {
+ public void testVerifyProgramInfo() throws Exception {
DecompileDebugFormatManager mngr = new DecompileDebugFormatManager(decompileDebugTestFile);
- DecompileDebugProgramInfo progInfo;
- MessageLog log = new MessageLog();
- try {
- progInfo = mngr.getProgramInfo();
- assertEquals("Spec string should be parsed correctly", "x86:LE:64:default",
- progInfo.specString());
- assertEquals("Compiler string should be extracted separately", "gcc",
- progInfo.compilerString());
- assertEquals("Memory offset should be parsed from the offset tag", "0x140022cb8",
- progInfo.offset());
- }
- catch (SAXException | IOException e) {
- log.appendException(e);
+ DecompileDebugProgramInfo progInfo = mngr.getProgramInfo();
+ assertEquals("Spec string should be parsed correctly", "x86:LE:64:default",
+ progInfo.specString());
+ assertEquals("Compiler string should be extracted separately", "gcc",
+ progInfo.compilerString());
+ assertEquals("Memory offset should be parsed from the offset tag", "0x140022cb8",
+ progInfo.offset());
+ }
+
+ @Test
+ public void testVerifyLoadedProgramBytes() throws Exception {
+ try (LoadResults loadResults = programLoader.load()) {
+ Program program = loadResults.getPrimaryDomainObject(this);
+ try {
+ assertEquals("gcc", program.getCompilerSpec().getCompilerSpecID().toString());
+
+ Memory memory = program.getMemory();
+ MemoryBlock[] blocks = memory.getBlocks();
+ assertEquals(26, blocks.length);
+
+ // Verify memory blocks
+ verifyBlock(blocks[0], "decompile_debug_test.xml", true,
+ getAddr(program, new BigInteger("140000000", 16)), 128);
+ verifyBlock(blocks[1], "__scrt_acquire_startup_lock", true,
+ getAddr(program, new BigInteger("1400227f8", 16)), 1);
+ verifyBlock(blocks[2], "FUN_140022834", true,
+ getAddr(program, new BigInteger("140022834", 16)), 1);
+ verifyBlock(blocks[3], "FUN_1400228fc", true,
+ getAddr(program, new BigInteger("1400228fc", 16)), 1);
+ verifyBlock(blocks[4], "__scrt_release_startup_lock", true,
+ getAddr(program, new BigInteger("140022994", 16)), 1);
+ verifyBlock(blocks[5], "__scrt_uninitialize_crt", true,
+ getAddr(program, new BigInteger("1400229b8", 16)), 1);
+ verifyBlock(blocks[6], "decompile_debug_test.xml", true,
+ getAddr(program, new BigInteger("140022cb8", 16)), 296);
+ verifyBlock(blocks[7], "decompile_debug_test.xml", true,
+ getAddr(program, new BigInteger("140022df9", 16)), 39);
+ }
+ finally {
+ program.release(this);
+ }
}
}
@Test
- public void testVerifyLoadedProgramBytes() {
- Collection loadSpecs;
- MessageLog log = new MessageLog();
- try {
- ByteProvider byteProvider =
- new FileByteProvider(decompileDebugTestFile, null, AccessMode.READ);
- loadSpecs = loader.findSupportedLoadSpecs(byteProvider);
+ public void testVerifyLoadedProgramDataTypes() throws Exception {
+ try (LoadResults