Merge remote-tracking branch 'origin/GP-5343_ryanmkurtz_mirroring'

(Closes #7430)
This commit is contained in:
Ryan Kurtz 2025-09-12 13:38:10 -04:00
commit 09eabbdcd9
68 changed files with 1494 additions and 1359 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.9 KiB

After

Width:  |  Height:  |  Size: 8.1 KiB

Before After
Before After

View file

@ -280,6 +280,12 @@
will be used to create a destination folder in the current project under the root folder
specified by the <B>Destination Folder</B> field.</LI><BR>
<LI><B>Mirror Filesystem</B> - If checked, the filesystem path layout of any imported
binaries will be mirrored in the destination folder. Any filesystem directory and file
soft links will be mirrored as
<A href="help/topics/FrontEndPlugin/Ghidra_Front_end.htm#Create_File_Links">Ghidra
project folder and file links.</A>.</LI><BR>
<LI><B>Options...</B> - This button will pop up format specific options for the
import.</LI><BR>
@ -335,7 +341,9 @@
<BLOCKQUOTE>
<P>The project folder that will get searched for existing library programs. If left
empty, the folder that the main program is being imported to will be searched.</P>
empty, the folder that the main program is being imported to will be searched.
<B><I>This option is hidden and set to the program destination folder if filesystem
mirroring is enabled in the Importer Dialog</I></B>.</P>
</BLOCKQUOTE>
<H4>Load Libraries From Disk</H4>
@ -360,7 +368,20 @@
<BLOCKQUOTE>
<P>The project folder where newly loaded library programs will get created. If left
empty, they will get created in the same folder as the main program being imported.</P>
empty, they will get created in the same folder as the main program being imported.
<B><I>This option is hidden and set to the program destination folder if filesystem
mirroring is enabled in the Importer Dialog</I></B>.</P>
</BLOCKQUOTE>
<H4>Mirror Library Disk Layout</H4>
<BLOCKQUOTE>
<P>If selected, the filesystem path layout of all imported libraries are
mirrored in the library destination folder. Any filesystem directory and file
soft links will be mirrored as
<A href="help/topics/FrontEndPlugin/Ghidra_Front_end.htm#Create_File_Links">Ghidra
project folder and file links</A>. <B><I>This option is hidden and enabled if filesystem
mirroring is enabled in the Importer Dialog</I></B>.</P>
</BLOCKQUOTE>
</BLOCKQUOTE>
@ -718,6 +739,9 @@
added via the
<A href="help/topics/FileSystemBrowserPlugin/FileSystemBrowserPlugin.html#FSB_Add_Library_Search_Path">File System Browser context menu</A>.
</P>
<P>If importing with filesystem mirroring activated, these paths also are used to lookup
already-imported libraries that are rooted in the project at the specified destination folder.
</P>
</BLOCKQUOTE>
<P align="center"><IMG alt="" src="images/SearchPathsDialog.png"></P>
@ -937,6 +961,16 @@
to the path the file was in its archive.</P>
</BLOCKQUOTE>
<H4>Mirror Filesystem</H4>
<BLOCKQUOTE>
<P>If selected, the filesystem path layout of any imported
binaries will be mirrored in the destination folder. Any filesystem directory and file
soft links will be mirrored as
<A href="help/topics/FrontEndPlugin/Ghidra_Front_end.htm#Create_File_Links">Ghidra
project folder and file links.</A></P>
</BLOCKQUOTE>
<H4>Project Destination</H4>
<BLOCKQUOTE>
<P>This shows the destination folder in the project that will be the root folder for storing

View file

@ -24,6 +24,7 @@ import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.bin.format.dwarf.external.*;
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.options.Options;
import ghidra.plugin.importer.ImporterUtilities;
@ -84,11 +85,13 @@ public class ExternalDebugFileSectionProvider extends BaseSectionProvider {
Loader origLoader = origLoadSpec.getLoader();
List<Option> defaultOptions = origLoader.getDefaultOptions(debugFileByteProvider,
origLoadSpec, debugProgram, false);
origLoadSpec, debugProgram, false, false);
ElfLoader elfLoader = new ElfLoader();
elfLoader.load(debugFileByteProvider, null, defaultOptions, debugProgram, monitor,
new MessageLog());
ImporterSettings settings =
new ImporterSettings(debugFileByteProvider, debugProgram.getName(), null, null,
false, origLoadSpec, defaultOptions, consumer, new MessageLog(), monitor);
elfLoader.load(debugProgram, settings);
ExternalDebugFileSectionProvider result = new ExternalDebugFileSectionProvider(
debugProgram, debugFileByteProvider.getFSRL());

View file

@ -52,6 +52,7 @@ public class AnalyzeHeadless implements GhidraLaunchable {
SCRIPT_LOG("-scriptlog", true, "<path to script log file>"),
LOG("-log", true, "<path to log file>"),
OVERWRITE("-overwrite", false),
MIRROR("-mirror", false),
RECURSIVE("-recursive", false),
READ_ONLY("-readOnly", false),
DELETE_PROJECT("-deleteProject", false),
@ -243,6 +244,9 @@ public class AnalyzeHeadless implements GhidraLaunchable {
else if (checkArgument(Arg.OVERWRITE, args, argi)) {
options.enableOverwriteOnConflict(true);
}
else if (checkArgument(Arg.MIRROR, args, argi)) {
options.enableMirroring(true);
}
else if (checkArgument(Arg.NO_ANALYSIS, args, argi)) {
options.enableAnalysis(false);
}

View file

@ -1543,6 +1543,7 @@ public class HeadlessAnalyzer {
.source(fsrl)
.project(project)
.projectFolderPath(folderPath)
.mirror(options.mirror)
.language(options.language)
.compiler(options.compilerSpec)
.loaders(options.loaderClass)

View file

@ -61,6 +61,9 @@ public class HeadlessOptions {
// -overwrite
boolean overwrite;
// -mirror
boolean mirror;
// -recursive
boolean recursive;
Integer recursiveDepth; // 'null' means use default depth, which is different for files vs dirs
@ -320,6 +323,15 @@ public class HeadlessOptions {
this.overwrite = enabled;
}
/**
* Enables/disables mirroring of the imported file(s) filesystem path in the project.
*
* @param enabled True if filesystem mirroring should happen; otherwise, false.
*/
public void enableMirroring(boolean enabled) {
this.mirror = enabled;
}
/**
* This method can be used to enable recursive processing of files during
* <code>-import</code> or <code>-process</code> modes. In order for recursive processing of

View file

@ -40,9 +40,10 @@ public class DomainFolderOption extends Option {
*
* @param name The name of the option
* @param arg The option's command line argument (could be null)
* @param hidden true if this option should be hidden from the user; otherwise, false
*/
public DomainFolderOption(String name, String arg) {
super(name, String.class, "", arg, null, Loader.OPTIONS_PROJECT_SAVE_STATE_KEY, false);
public DomainFolderOption(String name, String arg, boolean hidden) {
super(name, String.class, "", arg, null, Loader.OPTIONS_PROJECT_SAVE_STATE_KEY, hidden);
}
@Override
@ -85,6 +86,6 @@ public class DomainFolderOption extends Option {
@Override
public Option copy() {
return new DomainFolderOption(getName(), getArg());
return new DomainFolderOption(getName(), getArg(), isHidden());
}
}

View file

@ -85,14 +85,9 @@ public class LibrarySearchPathManager {
}
}
catch (MalformedURLException e) {
try {
File f = new File(path);
if (f.exists() && f.isAbsolute()) {
fsrl = fsService.getLocalFSRL(f.getCanonicalFile());
}
}
catch (IOException e2) {
log.appendException(e2);
fsrl = fsService.getLocalFSRL(f);
}
}
if (fsrl != null) {

View file

@ -24,6 +24,7 @@ import generic.stl.Pair;
import ghidra.app.util.Option;
import ghidra.app.util.bin.*;
import ghidra.app.util.opinion.*;
import ghidra.app.util.opinion.Loader.ImporterSettings;
import ghidra.formats.gfilesystem.FSRL;
import ghidra.formats.gfilesystem.FileSystemService;
import ghidra.framework.model.*;
@ -62,6 +63,7 @@ public class ProgramLoader {
private byte[] bytes;
private Project project;
private String projectFolderPath;
private boolean mirror;
private String importNameOverride;
private Predicate<Loader> loaderFilter = LoaderService.ACCEPT_ALL;
private List<Pair<String, String>> 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.
* <p>
* 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}.
* <p>
* 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}.
* <p>
@ -454,8 +468,21 @@ public class ProgramLoader {
LoadSpec loadSpec = getLoadSpec(p);
List<Option> loaderOptions = getLoaderOptions(p, loadSpec);
String importName = importNameOverride != null ? importNameOverride
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<Option> getLoaderOptions(ByteProvider p, LoadSpec loadSpec)
throws LanguageNotFoundException, LoadException {
List<Option> options = loadSpec.getLoader().getDefaultOptions(p, loadSpec, null, false);
List<Option> options =
loadSpec.getLoader().getDefaultOptions(p, loadSpec, null, false, mirror);
if (options == null) {
throw new LoadException("Cannot load with null options");
}

View file

@ -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<Option> getDefaultOptions(ByteProvider provider, LoadSpec loadSpec,
DomainObject domainObject, boolean loadIntoProgram) {
List<Option> list =
super.getDefaultOptions(provider, loadSpec, domainObject, loadIntoProgram);
DomainObject domainObject, boolean loadIntoProgram, boolean mirrorFsLayout) {
List<Option> 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<Option> 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<UnprocessedLibrary> unprocessed, int depth, LoadSpec loadSpec,
List<Option> options, MessageLog log, TaskMonitor monitor)
protected void processLibrary(Program lib, String libName, FSRL libFsrl,
Queue<UnprocessedLibrary> 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<Loaded<Program>> loadedPrograms, Project project,
LoadSpec loadSpec, List<Option> options, MessageLog messageLog, TaskMonitor monitor)
throws CancelledException, IOException {
protected void postLoadProgramFixups(List<Loaded<Program>> loadedPrograms,
ImporterSettings settings) throws CancelledException, IOException {
if (shouldPerformOrdinalLookup(options)) {
if (shouldPerformOrdinalLookup(settings)) {
List<Loaded<Program>> saveablePrograms = loadedPrograms
.stream()
.filter(loaded -> loaded.check(Predicate.not(Program::isTemporary)))
.toList();
monitor.initialize(saveablePrograms.size());
settings.monitor().initialize(saveablePrograms.size());
for (Loaded<Program> 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<Option> 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);
}

View file

@ -51,9 +51,9 @@ abstract class AbstractPeDebugLoader extends AbstractOrdinalSupportLoader {
@Override
public List<Option> getDefaultOptions(ByteProvider provider, LoadSpec loadSpec,
DomainObject domainObject, boolean loadIntoProgram) {
List<Option> list =
super.getDefaultOptions(provider, loadSpec, domainObject, loadIntoProgram);
DomainObject domainObject, boolean loadIntoProgram, boolean mirrorFsLayout) {
List<Option> 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;

View file

@ -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<Loaded<Program>> loadProgram(ByteProvider provider, String loadedName,
Project project, String projectFolderPath, LoadSpec loadSpec, List<Option> options,
MessageLog log, Object consumer, TaskMonitor monitor)
protected abstract List<Loaded<Program>> loadProgram(ImporterSettings settings)
throws IOException, LoadException, CancelledException;
/**
@ -102,40 +80,32 @@ public abstract class AbstractProgramLoader implements Loader {
* <p>
* 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<Option> 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<Option> 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<Loaded<Program>> loadedPrograms = loadProgram(provider, loadedName, project,
projectFolderPath, loadSpec, options, messageLog, consumer, monitor);
List<Loaded<Program>> loadedPrograms = loadProgram(settings);
boolean success = false;
try {
for (Loaded<Program> 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<Loaded<Program>> iter = loadedPrograms.iterator();
@ -168,11 +138,10 @@ public abstract class AbstractProgramLoader implements Loader {
}
@Override
public final void loadInto(ByteProvider provider, LoadSpec loadSpec, List<Option> 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<Option> getDefaultOptions(ByteProvider provider, LoadSpec loadSpec,
DomainObject domainObject, boolean isLoadIntoProgram) {
DomainObject domainObject, boolean loadIntoProgram, boolean mirrorFsLayout) {
ArrayList<Option> 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<Loaded<Program>> loadedPrograms, Project project,
LoadSpec loadSpec, List<Option> options, MessageLog messageLog, TaskMonitor monitor)
throws CancelledException, IOException {
protected void postLoadProgramFixups(List<Loaded<Program>> 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);
}

View file

@ -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<Option> options,
Program program, TaskMonitor monitor, MessageLog log)
protected abstract void load(Program program, ImporterSettings settings)
throws CancelledException, IOException;
@Override
protected List<Loaded<Program>> loadProgram(ByteProvider provider, String programName,
Project project, String programFolderPath, LoadSpec loadSpec, List<Option> options,
MessageLog log, Object consumer, TaskMonitor monitor)
protected List<Loaded<Program>> 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<Loaded<Program>> loadedList = List.of(
new Loaded<Program>(program, programName, project, programFolderPath, consumer));
Program program = createProgram(settings);
Loaded<Program> loaded = new Loaded<Program>(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<Option> 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

View file

@ -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<Loaded<Program>> loadProgram(ByteProvider provider, String programName,
Project project, String programFolderPath, LoadSpec loadSpec, List<Option> options,
MessageLog log, Object consumer, TaskMonitor monitor)
protected List<Loaded<Program>> 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<Loaded<Program>> loadedList =
List.of(new Loaded<>(prog, programName, project, programFolderPath, consumer));
Program prog = createProgram(baseAddr, settings);
Loaded<Program> loaded = new Loaded<Program>(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<Option> 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<Option> 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;
}

View file

@ -123,9 +123,9 @@ public class CoffLoader extends AbstractLibrarySupportLoader {
@Override
public List<Option> getDefaultOptions(ByteProvider provider, LoadSpec loadSpec,
DomainObject domainObject, boolean loadIntoProgram) {
List<Option> list =
super.getDefaultOptions(provider, loadSpec, domainObject, loadIntoProgram);
DomainObject domainObject, boolean loadIntoProgram, boolean mirrorFsLayout) {
List<Option> 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<Option> 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<CoffSectionHeader, Address> sectionsMap = new HashMap<>();
Map<CoffSymbol, Symbol> 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);

View file

@ -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<Option> 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) {

View file

@ -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
* <hr>
* {@inheritDoc}
*/
@Override
protected List<Loaded<Program>> loadProgram(ByteProvider provider, String programName,
Project project,
String programFolderPath, LoadSpec loadSpec, List<Option> options, MessageLog log,
Object consumer,
TaskMonitor monitor) throws IOException, CancelledException {
protected List<Loaded<Program>> 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<Loaded<Program>>();
}
@ -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<Loaded<Program>> loadedList =
List.of(new Loaded<>(prog, programName, project, programFolderPath, consumer));
Program prog = createProgram(imageBase, settings);
List<Loaded<Program>> 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<Option> 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.

View file

@ -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<Option> 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<String> errorConsumer = err -> log.appendMsg("DefLoader", err);
for (DefExportLine def : parseExports(provider)) {
Consumer<String> 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());
}
}
}

View file

@ -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<Option> 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<Option> getDefaultOptions(ByteProvider provider, LoadSpec loadSpec,
DomainObject domainObject, boolean loadIntoProgram) {
List<Option> list =
super.getDefaultOptions(provider, loadSpec, domainObject, loadIntoProgram);
DomainObject domainObject, boolean loadIntoProgram, boolean mirrorFsLayout) {
List<Option> 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,

View file

@ -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<Option> getDefaultOptions(ByteProvider provider, LoadSpec loadSpec,
DomainObject domainObject, boolean loadIntoProgram) {
DomainObject domainObject, boolean loadIntoProgram, boolean mirrorFsLayout) {
// NOTE: add-to-program is not supported
List<Option> options =
super.getDefaultOptions(provider, loadSpec, domainObject, loadIntoProgram);
List<Option> options = super.getDefaultOptions(provider, loadSpec, domainObject,
loadIntoProgram, mirrorFsLayout);
try {
ElfLoaderOptionsFactory.addOptions(options, provider, loadSpec);
@ -138,13 +137,14 @@ public class ElfLoader extends AbstractLibrarySupportLoader {
}
@Override
public void load(ByteProvider provider, LoadSpec loadSpec, List<Option> 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<Loaded<Program>> loadedPrograms, Project project,
LoadSpec loadSpec, List<Option> options, MessageLog messageLog, TaskMonitor monitor)
throws CancelledException, IOException {
super.postLoadProgramFixups(loadedPrograms, project, loadSpec, options, messageLog,
monitor);
protected void postLoadProgramFixups(List<Loaded<Program>> 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);
}
}

View file

@ -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<Option> 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<Option> 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<Option> 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");
}

View file

@ -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<Option> 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<Option> 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<Option> 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");
}

View file

@ -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<Option> options, Program program) {
public String validateOptions(ByteProvider provider, LoadSpec loadSpec, List<Option> options,
Program program) {
Address baseAddr = null;
for (Option option : options) {
@ -142,23 +142,15 @@ public class IntelHexLoader extends AbstractProgramLoader {
}
@Override
protected List<Loaded<Program>> loadProgram(ByteProvider provider, String programName,
Project project, String programFolderPath, LoadSpec loadSpec, List<Option> options,
MessageLog log, Object consumer, TaskMonitor monitor)
protected List<Loaded<Program>> 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<Loaded<Program>> loadedList =
List.of(new Loaded<>(prog, programName, project, programFolderPath, consumer));
Program prog = createProgram(null, settings);
List<Loaded<Program>> 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<Option> 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<Option> getDefaultOptions(ByteProvider provider, LoadSpec loadSpec,
DomainObject domainObject, boolean loadIntoProgram) {
DomainObject domainObject, boolean loadIntoProgram, boolean mirrorFsLayout) {
String blockName = "";
boolean isOverlay = false;
Address baseAddr = null;

View file

@ -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 {

View file

@ -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 <T> The type of {@link DomainObject}s that were loaded
*/
@ -54,29 +54,14 @@ public class LoadResults<T extends DomainObject> implements Iterable<Loaded<T>>,
}
/**
* 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<T>(domainObject, name, project, projectFolderPath, consumer)));
public LoadResults(Loaded<T> loaded) {
this(List.of(loaded));
}
/**
@ -155,9 +140,11 @@ public class LoadResults<T extends DomainObject> implements Iterable<Loaded<T>>,
* @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<T> loaded : loadedList) {
loaded.save(monitor);
}

View file

@ -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<T extends DomainObject> 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<T extends DomainObject> 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.
* <p>
* 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<T extends DomainObject> 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<T extends DomainObject> 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,11 +260,16 @@ public class Loaded<T extends DomainObject> 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);
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);
@ -236,10 +280,7 @@ public class Loaded<T extends DomainObject> implements AutoCloseable {
++uniqueNameIndex;
}
}
}
catch (InvalidNameException e) {
throw new IOException(e);
}
throw new CancelledException();
}
@ -294,4 +335,124 @@ public class Loaded<T extends DomainObject> 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<String> 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);
}
}

View file

@ -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<T extends DomainObject> extends Loaded<T> {
*
* @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<T extends DomainObject> extends Loaded<T> {
* 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");

View file

@ -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<Loader> {
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<Option> 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<Loader> {
* 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<Loader> {
* @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<Option> 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<Option> 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<Loader> {
* @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<Option> 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,

View file

@ -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<String> getLibraryNameComparator() {
return String.CASE_INSENSITIVE_ORDER;
}
@Override

View file

@ -109,11 +109,14 @@ public class MachoLoader extends AbstractLibrarySupportLoader {
}
@Override
public void load(ByteProvider provider, LoadSpec loadSpec, List<Option> 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<Option> getDefaultOptions(ByteProvider provider, LoadSpec loadSpec,
DomainObject domainObject, boolean loadIntoProgram) {
List<Option> list =
super.getDefaultOptions(provider, loadSpec, domainObject, loadIntoProgram);
DomainObject domainObject, boolean loadIntoProgram, boolean mirrorFsLayout) {
List<Option> 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<GFile> 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:
* <p>
* {@code /System/Library/Frameworks/Foundation.framework/Foundation}
* <p>
* might be found at:
* <p>
* {@code /System/Library/Frameworks/Foundation.framework/Versions/C/Foundation}
* <hr>
* {@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:
* <p>
* {@code /System/Library/Frameworks/Foundation.framework/Foundation}
* <p>
* might be found at:
* <p>
* {@code /System/Library/Frameworks/Foundation.framework/Versions/C/Foundation}
* <hr>
* {@inheritDoc}
*/
@Override
protected Comparator<String> getLibraryNameComparator() {
String versionRegex = "Versions/.+/";
return (s1, s2) -> s1.replaceAll(versionRegex, "")
.compareTo(s2.replaceAll(versionRegex, ""));
}
/**
* {@inheritDoc}
* <p>
@ -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<Option> 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,7 +369,8 @@ public class MachoLoader extends AbstractLibrarySupportLoader {
}
}
catch (Exception e) {
log.appendMsg("Failed to parse Mach-O header for: '%s': %s"
settings.log()
.appendMsg("Failed to parse Mach-O header for: '%s': %s"
.formatted(program.getName(), e.getMessage()));
}
}
@ -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<UnprocessedLibrary> unprocessed, int depth, LoadSpec loadSpec,
List<Option> options, MessageLog log, TaskMonitor monitor)
protected void processLibrary(Program lib, String libName, FSRL libFsrl,
Queue<UnprocessedLibrary> 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<Loaded<Program>> loadedPrograms, Project project,
LoadSpec loadSpec, List<Option> options, MessageLog log, TaskMonitor monitor)
throws CancelledException, IOException {
protected void postLoadProgramFixups(List<Loaded<Program>> loadedPrograms,
ImporterSettings settings) throws CancelledException, IOException {
if (shouldPerformReexports(options)) {
MessageLog log = settings.log();
TaskMonitor monitor = settings.monitor();
List<DomainFolder> searchFolders =
getLibrarySearchFolders(loadedPrograms, project, options, log);
if (shouldPerformReexports(settings)) {
List<DomainFolder> searchFolders = getLibrarySearchFolders(loadedPrograms, settings);
Program firstProgram = loadedPrograms.getFirst().getDomainObject(this);
List<LibrarySearchPath> 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<Option> 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<Loaded<Program>> loadedPrograms,
List<DomainFolder> searchFolders, List<LibrarySearchPath> searchPaths,
List<Option> 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) {

View file

@ -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<Option> 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<MapSymbol> symbols = parseMapFile(provider, monitor, log);
List<MapSymbol> symbols = parseMapFile(settings.provider(), monitor, log);
monitor.initialize(symbols.size(), "Creating symbols...");
for (MapSymbol symbol : symbols) {
monitor.increment();

View file

@ -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<Loaded<Program>> loadProgram(ByteProvider provider, String programName,
Project project, String programFolderPath, LoadSpec loadSpec, List<Option> options,
MessageLog log, Object consumer, TaskMonitor monitor)
protected List<Loaded<Program>> 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<Loaded<Program>> loadedList =
List.of(new Loaded<>(prog, programName, project, programFolderPath, consumer));
Program prog = createProgram(null, settings);
List<Loaded<Program>> 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<Option> 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<Option> getDefaultOptions(ByteProvider provider, LoadSpec loadSpec,
DomainObject domainObject, boolean loadIntoProgram) {
DomainObject domainObject, boolean loadIntoProgram, boolean mirrorFsLayout) {
String blockName = "";
boolean isOverlay = false;
Address baseAddr = null;

View file

@ -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<Option> 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<RelocationFixup> relocationFixups = getRelocationFixups(space, mz, log, monitor);

View file

@ -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<Option> 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<String> getLibraryNameComparator() {
return (s1, s2) -> String.CASE_INSENSITIVE_ORDER.compare(FilenameUtils.getBaseName(s1),
FilenameUtils.getBaseName(s2));
}
//////////////////////////////////////////////////////////////////

View file

@ -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<Option> 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<OmfRecord> records = OmfUtils.readRecords(factory);
Map<Integer, Address> segmentToAddr =

View file

@ -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<Option> 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);

View file

@ -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<Option> 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<SectionHeader, Address> 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<Option> getDefaultOptions(ByteProvider provider, LoadSpec loadSpec,
DomainObject domainObject, boolean loadIntoProgram) {
List<Option> list =
super.getDefaultOptions(provider, loadSpec, domainObject, loadIntoProgram);
DomainObject domainObject, boolean loadIntoProgram, boolean mirrorFsLayout) {
List<Option> 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<String> getLibraryNameComparator() {
return (s1, s2) -> String.CASE_INSENSITIVE_ORDER.compare(FilenameUtils.getName(s1),
FilenameUtils.getName(s2));
}
private boolean shouldParseCliHeaders(List<Option> options) {

View file

@ -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<Option> 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();

View file

@ -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<Option> 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<Option> 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;
}

View file

@ -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<Loaded<Program>> loadProgram(ByteProvider provider, String programName,
Project project, String programFolderPath, LoadSpec loadSpec, List<Option> options,
MessageLog log, Object consumer, TaskMonitor monitor)
protected List<Loaded<Program>> loadProgram(ImporterSettings settings)
throws IOException, LoadException, CancelledException {
List<Loaded<Program>> 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<Loaded<Program>> loadedList =
List.of(new Loaded<>(prog, programName, project, programFolderPath, consumer));
Program prog = createProgram(imageBase, settings);
List<Loaded<Program>> 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<Option> 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<Option> options,
@ -325,7 +319,7 @@ public class XmlLoader extends AbstractProgramLoader {
@Override
public List<Option> 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<Option> options, Program program) {
public String validateOptions(ByteProvider provider, LoadSpec loadSpec, List<Option> options,
Program program) {
// XXX will this work? is there other state that xmlOptions needs to
// know?
try {

View file

@ -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;
}
}

View file

@ -179,7 +179,7 @@ public class FileSystemIndexHelper<METADATATYPE> {
try {
FileData<METADATATYPE> baseDirData = getFileData(baseDir);
FileData<METADATATYPE> 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<METADATATYPE> {
symlinkPathDebug.append("[");
FileData<METADATATYPE> 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<METADATATYPE> {
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<METADATATYPE> {
*/
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<METADATATYPE> {
return parent.file;
}
protected String[] splitPath(String path) {
return Objects.requireNonNullElse(path, "").replace('\\', '/').split("/");
}
protected FileData<METADATATYPE> lookupFileInDir(
Map<String, FileData<METADATATYPE>> dirContents, String filename,
Comparator<String> nameComp) {

View file

@ -80,4 +80,9 @@ public class FileSystemRef implements Closeable {
Msg.warn(this, "Unclosed FilesytemRef: " + fs.toString());
}
}
@Override
public String toString() {
return fs.getFSRL().toString();
}
}

View file

@ -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<Option> getOptions(LoadSpec loadSpec) {
if (options != null) {
protected List<Option> 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);
}
/**

View file

@ -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<Loader> 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,18 +348,19 @@ public class ImporterDialog extends DialogComponentProvider {
@Override
protected void okCallback() {
if (validateFormInput()) {
if (!validateFormInput()) {
return;
}
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.
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, importFolder,
loadSpec, programName, options, monitor);
ImporterUtilities.importSingleFile(tool, programManager, fsrl,
destinationFolder.getPathname(), mirrorFsCheckBox.isSelected(), loadSpec,
removeTrailingSlashes(getName()), options, monitor);
})
.setLaunchDelay(0)
.launchNonModal();
@ -357,7 +368,6 @@ public class ImporterDialog extends DialogComponentProvider {
close();
}
}
private String removeTrailingSlashes(String path) {
while (path.endsWith("/")) {
@ -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<Option> getOptions(LoadSpec loadSpec) {
if (options == null) {
options = loadSpec.getLoader().getDefaultOptions(byteProvider, loadSpec, null, false);
protected List<Option> 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<Option> currentOptions = getOptions(loadSpec);
List<Option> currentOptions = getOptions(loadSpec, true);
if (currentOptions.isEmpty()) {
Msg.showInfo(this, null, "Options", "There are no options for this importer!");
return;

View file

@ -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));

View file

@ -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<Option> options,
TaskMonitor monitor) {
String projectRootPath, boolean mirrorFsLayout, LoadSpec loadSpec, String importName,
List<Option> 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<DomainFile> 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<DomainFile> 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);
}
}

View file

@ -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<Option> getLoadLibraryOptions(ByteProvider provider, LoadSpec loadSpec) {
List<Option> 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:

View file

@ -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;

View file

@ -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<DomainFolder, String> 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<Option> getOptionsFor(BatchLoadConfig batchLoadConfig, LoadSpec loadSpec,
ByteProvider byteProvider) {
List<Option> options =
batchLoadConfig.getLoader().getDefaultOptions(byteProvider, loadSpec, null, false);
List<Option> options = batchLoadConfig.getLoader()
.getDefaultOptions(byteProvider, loadSpec, null, false, false);
return options;
}
}

View file

@ -21,86 +21,54 @@ 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<LoadSpec> 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<Option> 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<Program> 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();
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",
@ -108,28 +76,12 @@ public class DecompileDebugXmlLoaderTest extends AbstractGhidraHeadedIntegration
assertEquals("Memory offset should be parsed from the offset tag", "0x140022cb8",
progInfo.offset());
}
catch (SAXException | IOException e) {
log.appendException(e);
}
}
@Test
public void testVerifyLoadedProgramBytes() {
Collection<LoadSpec> loadSpecs;
MessageLog log = new MessageLog();
public void testVerifyLoadedProgramBytes() throws Exception {
try (LoadResults<Program> loadResults = programLoader.load()) {
Program program = loadResults.getPrimaryDomainObject(this);
try {
ByteProvider byteProvider =
new FileByteProvider(decompileDebugTestFile, null, AccessMode.READ);
loadSpecs = loader.findSupportedLoadSpecs(byteProvider);
LoadSpec loadSpec = loadSpecs.iterator().next();
List<Option> options = loader.getDefaultOptions(byteProvider, loadSpec, null, false);
LoadResults<? extends DomainObject> loadResults =
loader.load(byteProvider, byteProvider.getName(), null, null, loadSpec, options,
new MessageLog(), this, TaskMonitor.DUMMY);
loadResults.getNonPrimary();
program = (ProgramDB) loadResults.getPrimaryDomainObject(this);
assertEquals("gcc", program.getCompilerSpec().getCompilerSpecID().toString());
Memory memory = program.getMemory();
@ -138,47 +90,35 @@ public class DecompileDebugXmlLoaderTest extends AbstractGhidraHeadedIntegration
// Verify memory blocks
verifyBlock(blocks[0], "decompile_debug_test.xml", true,
getAddr(new BigInteger("140000000", 16)), 128);
getAddr(program, new BigInteger("140000000", 16)), 128);
verifyBlock(blocks[1], "__scrt_acquire_startup_lock", true,
getAddr(new BigInteger("1400227f8", 16)), 1);
getAddr(program, new BigInteger("1400227f8", 16)), 1);
verifyBlock(blocks[2], "FUN_140022834", true,
getAddr(new BigInteger("140022834", 16)), 1);
getAddr(program, new BigInteger("140022834", 16)), 1);
verifyBlock(blocks[3], "FUN_1400228fc", true,
getAddr(new BigInteger("1400228fc", 16)), 1);
getAddr(program, new BigInteger("1400228fc", 16)), 1);
verifyBlock(blocks[4], "__scrt_release_startup_lock", true,
getAddr(new BigInteger("140022994", 16)), 1);
getAddr(program, new BigInteger("140022994", 16)), 1);
verifyBlock(blocks[5], "__scrt_uninitialize_crt", true,
getAddr(new BigInteger("1400229b8", 16)), 1);
getAddr(program, new BigInteger("1400229b8", 16)), 1);
verifyBlock(blocks[6], "decompile_debug_test.xml", true,
getAddr(new BigInteger("140022cb8", 16)), 296);
getAddr(program, new BigInteger("140022cb8", 16)), 296);
verifyBlock(blocks[7], "decompile_debug_test.xml", true,
getAddr(new BigInteger("140022df9", 16)), 39);
getAddr(program, new BigInteger("140022df9", 16)), 39);
}
finally {
program.release(this);
}
catch (IOException | CancelledException | VersionException e) {
log.appendException(e);
}
}
@Test
public void testVerifyLoadedProgramDataTypes() {
Collection<LoadSpec> loadSpecs;
MessageLog log = new MessageLog();
public void testVerifyLoadedProgramDataTypes() throws Exception {
try (LoadResults<Program> loadResults = programLoader.load()) {
Program program = loadResults.getPrimaryDomainObject(this);
try {
ByteProvider byteProvider =
new FileByteProvider(decompileDebugTestFile, null, AccessMode.READ);
loadSpecs = loader.findSupportedLoadSpecs(byteProvider);
LoadSpec loadSpec = loadSpecs.iterator().next();
List<Option> options = loader.getDefaultOptions(byteProvider, loadSpec, null, false);
LoadResults<? extends DomainObject> loadResults =
loader.load(byteProvider, byteProvider.getName(), null, null, loadSpec, options,
new MessageLog(), this, TaskMonitor.DUMMY);
loadResults.getNonPrimary();
program = (ProgramDB) loadResults.getPrimaryDomainObject(this);
// Verify data type generation
ProgramDataTypeManager dtm = program.getDataTypeManager();
ProgramBasedDataTypeManager dtm = program.getDataTypeManager();
assertEquals("Category count didn't match. ", 2, dtm.getCategoryCount());
Iterator<Structure> structures = dtm.getAllStructures();
@ -189,30 +129,19 @@ public class DecompileDebugXmlLoaderTest extends AbstractGhidraHeadedIntegration
assertEquals("Array component name doesn't match", "e_magic", array.getFieldName());
assertEquals("Array wasn't sized right", 2, array.getLength());
}
catch (IOException | CancelledException | VersionException e) {
log.appendException(e);
finally {
program.release(this);
}
}
}
@Test
public void verifyLoadedProgramFunctions() {
Collection<LoadSpec> loadSpecs;
MessageLog log = new MessageLog();
public void verifyLoadedProgramFunctions() throws Exception {
try (LoadResults<Program> loadResults = programLoader.load()) {
Program program = loadResults.getPrimaryDomainObject(this);
try {
ByteProvider byteProvider =
new FileByteProvider(decompileDebugTestFile, null, AccessMode.READ);
loadSpecs = loader.findSupportedLoadSpecs(byteProvider);
LoadSpec loadSpec = loadSpecs.iterator().next();
List<Option> options = loader.getDefaultOptions(byteProvider, loadSpec, null, false);
LoadResults<? extends DomainObject> loadResults =
loader.load(byteProvider, byteProvider.getName(), null, null, loadSpec, options,
new MessageLog(), this, TaskMonitor.DUMMY);
loadResults.getNonPrimary();
program = (ProgramDB) loadResults.getPrimaryDomainObject(this);
// Verify function collection
FunctionManagerDB funcMngr = program.getFunctionManager();
FunctionManager funcMngr = program.getFunctionManager();
assertEquals("Function count doesn't match", 19, funcMngr.getFunctionCount());
Iterator<Function> functions = funcMngr.getFunctions(true);
@ -220,8 +149,9 @@ public class DecompileDebugXmlLoaderTest extends AbstractGhidraHeadedIntegration
assertEquals("Function name needs to match.", "__scrt_acquire_startup_lock",
function.getName());
}
catch (IOException | CancelledException | VersionException e) {
log.appendException(e);
finally {
program.release(this);
}
}
}
@ -233,7 +163,7 @@ public class DecompileDebugXmlLoaderTest extends AbstractGhidraHeadedIntegration
assertEquals("Block length didn't match", length, block.getSize());
}
private Address getAddr(BigInteger offset) {
private Address getAddr(Program program, BigInteger offset) {
return program.getAddressFactory().getDefaultAddressSpace().getAddress(offset.longValue());
}
}

View file

@ -21,7 +21,6 @@ import java.util.*;
import org.jdom.*;
import org.jdom.input.SAXBuilder;
import ghidra.app.util.Option;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.importer.MessageLog;
@ -37,7 +36,6 @@ import ghidra.file.formats.android.xml.AndroidXmlFileSystem;
import ghidra.file.formats.zip.ZipFileSystem;
import ghidra.formats.gfilesystem.FileSystemService;
import ghidra.formats.gfilesystem.GFile;
import ghidra.framework.model.Project;
import ghidra.program.model.listing.Program;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
@ -64,15 +62,15 @@ public class ApkLoader extends DexLoader {
}
@Override
protected List<Loaded<Program>> loadProgram(ByteProvider provider, String programName,
Project project, String programFolderPath, LoadSpec loadSpec, List<Option> options,
MessageLog log, Object consumer, TaskMonitor monitor)
protected List<Loaded<Program>> loadProgram(ImporterSettings settings)
throws IOException, LoadException, CancelledException {
MessageLog log = settings.log();
TaskMonitor monitor = settings.monitor();
boolean success = false;
List<Loaded<Program>> allLoadedPrograms = new ArrayList<>();
int dexIndex = 1;//DEX file numbering starts at 1
try (ZipFileSystem zipFS = openAPK(provider, monitor)) {
try (ZipFileSystem zipFS = openAPK(settings.provider(), monitor)) {
while (!monitor.isCancelled()) {
GFile classesDexFile =
zipFS.lookup("/" + "classes" + (dexIndex == 1 ? "" : dexIndex) + ".dex");
@ -81,16 +79,17 @@ public class ApkLoader extends DexLoader {
break;
}
monitor.setMessage(
"Loading " + classesDexFile.getName() + " from " + programName + "...");
monitor.setMessage("Loading " + classesDexFile.getName() + " from " +
settings.importName() + "...");
try (ByteProvider dexProvider =
zipFS.getByteProvider(classesDexFile, monitor)) {
// defer to the super class (DexLoader) to actually load the DEX file
List<Loaded<Program>> loadedPrograms =
super.loadProgram(dexProvider, classesDexFile.getName(), project,
joinPaths(programFolderPath, programName), loadSpec, options, log,
consumer, monitor);
ImporterSettings newSettings = new ImporterSettings(dexProvider,
classesDexFile.getName(), settings.project(), settings.projectRootPath(),
settings.mirrorFsLayout(), settings.loadSpec(), settings.options(),
settings.consumer(), settings.log(), settings.monitor());
List<Loaded<Program>> loadedPrograms = super.loadProgram(newSettings);
allLoadedPrograms.addAll(loadedPrograms);
}

View file

@ -72,21 +72,21 @@ public class DexLoader extends AbstractProgramWrapperLoader {
}
@Override
public void load(ByteProvider provider, LoadSpec loadSpec, List<Option> options,
Program program, TaskMonitor monitor, MessageLog log) throws IOException {
public void load(Program program, ImporterSettings settings) throws IOException {
MessageLog log = settings.log();
TaskMonitor monitor = settings.monitor();
monitor.setMessage(getMonitorMessagePrimary());
try {
Address start = program.getAddressFactory().getDefaultAddressSpace().getAddress(0x0);
long length = provider.length();
long length = settings.provider().length();
try (InputStream inputStream = provider.getInputStream(0)) {
try (InputStream inputStream = settings.provider().getInputStream(0)) {
program.getMemory()
.createInitializedBlock(getMemoryBlockName(), start, inputStream, length,
monitor, false);
}
BinaryReader reader = new BinaryReader(provider, true);
BinaryReader reader = new BinaryReader(settings.provider(), true);
DexHeader header = DexHeaderFactory.getDexHeader(reader);
monitor.setMessage(getMonitorMessageSecondary());
@ -244,7 +244,7 @@ public class DexLoader extends AbstractProgramWrapperLoader {
@Override
public List<Option> getDefaultOptions(ByteProvider provider, LoadSpec loadSpec,
DomainObject domainObject, boolean loadIntoProgram) {
DomainObject domainObject, boolean loadIntoProgram, boolean mirrorFsLayout) {
return Collections.emptyList();
}

View file

@ -26,7 +26,6 @@ import ghidra.file.formats.ios.dyldcache.DyldCacheExtractor;
import ghidra.file.formats.ios.dyldcache.DyldCacheFileSystem;
import ghidra.formats.gfilesystem.*;
import ghidra.framework.model.DomainObject;
import ghidra.framework.model.Project;
import ghidra.program.database.mem.FileBytes;
import ghidra.program.model.listing.Group;
import ghidra.program.model.listing.Program;
@ -77,14 +76,14 @@ public class DyldCacheExtractLoader extends MachoLoader {
}
@Override
public void load(ByteProvider provider, LoadSpec loadSpec, List<Option> options,
Program program, TaskMonitor monitor, MessageLog log) throws IOException {
public void load(Program program, ImporterSettings settings) throws IOException {
try {
FileBytes fileBytes = MemoryBlockUtils.createFileBytes(program, provider, monitor);
MachoExtractProgramBuilder.buildProgram(program, provider, fileBytes, false, log,
monitor);
addOptionalComponents(program, options, log, monitor);
FileBytes fileBytes =
MemoryBlockUtils.createFileBytes(program, settings.provider(), settings.monitor());
MachoExtractProgramBuilder.buildProgram(program, settings.provider(), fileBytes, false,
settings.log(), settings.monitor());
addOptionalComponents(program, settings.options(), settings.log(), settings.monitor());
}
catch (CancelledException e) {
return;
@ -98,20 +97,20 @@ public class DyldCacheExtractLoader extends MachoLoader {
}
@Override
protected void loadProgramInto(ByteProvider provider, LoadSpec loadSpec,
List<Option> options, MessageLog log, Program program, TaskMonitor monitor)
protected void loadProgramInto(Program program, ImporterSettings settings)
throws IOException, LoadException, CancelledException {
FSRL fsrl = provider.getFSRL();
FSRL fsrl = settings.provider().getFSRL();
Group[] children = program.getListing().getDefaultRootModule().getChildren();
if (Arrays.stream(children).anyMatch(e -> e.getName().contains(fsrl.getPath()))) {
log.appendMsg("%s has already been added".formatted(fsrl.getPath()));
settings.log().appendMsg("%s has already been added".formatted(fsrl.getPath()));
return;
}
try {
FileBytes fileBytes = MemoryBlockUtils.createFileBytes(program, provider, monitor);
MachoExtractProgramBuilder.buildProgram(program, provider, fileBytes, true, log,
monitor);
addOptionalComponents(program, options, log, monitor);
FileBytes fileBytes =
MemoryBlockUtils.createFileBytes(program, settings.provider(), settings.monitor());
MachoExtractProgramBuilder.buildProgram(program, settings.provider(), fileBytes, true,
settings.log(), settings.monitor());
addOptionalComponents(program, settings.options(), settings.log(), settings.monitor());
}
catch (CancelledException e) {
return;
@ -141,7 +140,7 @@ public class DyldCacheExtractLoader extends MachoLoader {
@Override
public List<Option> getDefaultOptions(ByteProvider provider, LoadSpec loadSpec,
DomainObject domainObject, boolean loadIntoProgram) {
DomainObject domainObject, boolean loadIntoProgram, boolean mirrorFsLayout) {
List<Option> list = new ArrayList<>();
list.add(new Option(LIBOBJC_OPTION_NAME, !loadIntoProgram && LIBOBJC_OPTION_DEFAULT,
Boolean.class, Loader.COMMAND_LINE_ARG_PREFIX + "-libobjc"));
@ -185,19 +184,18 @@ public class DyldCacheExtractLoader extends MachoLoader {
}
@Override
protected boolean isLoadLibraries(List<Option> options) {
protected boolean isLoadLibraries(ImporterSettings settings) {
return false;
}
@Override
protected boolean shouldSearchAllPaths(Program program, List<Option> options, MessageLog log) {
protected boolean shouldSearchAllPaths(Program program, ImporterSettings settings) {
return false;
}
@Override
protected void postLoadProgramFixups(List<Loaded<Program>> loadedPrograms, Project project,
LoadSpec loadSpec, List<Option> options, MessageLog messageLog, TaskMonitor monitor)
throws CancelledException, IOException {
protected void postLoadProgramFixups(List<Loaded<Program>> loadedPrograms,
ImporterSettings settings) throws CancelledException, IOException {
// Do nothing
}

View file

@ -21,16 +21,13 @@ import java.util.*;
import ghidra.app.util.MemoryBlockUtils;
import ghidra.app.util.Option;
import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.importer.MessageLog;
import ghidra.file.formats.ios.fileset.MachoFileSetExtractor;
import ghidra.formats.gfilesystem.FSRL;
import ghidra.framework.model.DomainObject;
import ghidra.framework.model.Project;
import ghidra.program.database.mem.FileBytes;
import ghidra.program.model.listing.Group;
import ghidra.program.model.listing.Program;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
/**
* A {@link Loader} for Mach-O file set entries extracted by Ghidra from a Mach-O file set
@ -52,13 +49,13 @@ public class MachoFileSetExtractLoader extends MachoLoader {
}
@Override
public void load(ByteProvider provider, LoadSpec loadSpec, List<Option> options,
Program program, TaskMonitor monitor, MessageLog log) throws IOException {
public void load(Program program, ImporterSettings settings) throws IOException {
try {
FileBytes fileBytes = MemoryBlockUtils.createFileBytes(program, provider, monitor);
MachoExtractProgramBuilder.buildProgram(program, provider, fileBytes, false, log,
monitor);
FileBytes fileBytes =
MemoryBlockUtils.createFileBytes(program, settings.provider(), settings.monitor());
MachoExtractProgramBuilder.buildProgram(program, settings.provider(), fileBytes, false,
settings.log(), settings.monitor());
}
catch (CancelledException e) {
return;
@ -72,19 +69,19 @@ public class MachoFileSetExtractLoader extends MachoLoader {
}
@Override
protected void loadProgramInto(ByteProvider provider, LoadSpec loadSpec, List<Option> options,
MessageLog log, Program program, TaskMonitor monitor)
protected void loadProgramInto(Program program, ImporterSettings settings)
throws IOException, LoadException, CancelledException {
FSRL fsrl = provider.getFSRL();
FSRL fsrl = settings.provider().getFSRL();
Group[] children = program.getListing().getDefaultRootModule().getChildren();
if (Arrays.stream(children).anyMatch(e -> e.getName().contains(fsrl.getPath()))) {
log.appendMsg("%s has already been added".formatted(fsrl.getPath()));
settings.log().appendMsg("%s has already been added".formatted(fsrl.getPath()));
return;
}
try {
FileBytes fileBytes = MemoryBlockUtils.createFileBytes(program, provider, monitor);
MachoExtractProgramBuilder.buildProgram(program, provider, fileBytes, true, log,
monitor);
FileBytes fileBytes =
MemoryBlockUtils.createFileBytes(program, settings.provider(), settings.monitor());
MachoExtractProgramBuilder.buildProgram(program, settings.provider(), fileBytes, true,
settings.log(), settings.monitor());
}
catch (CancelledException e) {
return;
@ -114,24 +111,23 @@ public class MachoFileSetExtractLoader extends MachoLoader {
@Override
public List<Option> getDefaultOptions(ByteProvider provider, LoadSpec loadSpec,
DomainObject domainObject, boolean loadIntoProgram) {
DomainObject domainObject, boolean loadIntoProgram, boolean mirrorFsLayout) {
return List.of();
}
@Override
protected boolean isLoadLibraries(List<Option> options) {
protected boolean isLoadLibraries(ImporterSettings settings) {
return false;
}
@Override
protected boolean shouldSearchAllPaths(Program program, List<Option> options, MessageLog log) {
protected boolean shouldSearchAllPaths(Program program, ImporterSettings settings) {
return false;
}
@Override
protected void postLoadProgramFixups(List<Loaded<Program>> loadedPrograms, Project project,
LoadSpec loadSpec, List<Option> options, MessageLog messageLog, TaskMonitor monitor)
throws CancelledException, IOException {
protected void postLoadProgramFixups(List<Loaded<Program>> loadedPrograms,
ImporterSettings settings) throws CancelledException, IOException {
// Do nothing
}
}

View file

@ -106,12 +106,11 @@ public class DumpFileLoader extends AbstractProgramWrapperLoader {
}
@Override
@SuppressWarnings("hiding")
protected void load(ByteProvider provider, LoadSpec loadSpec, List<Option> options,
Program program, TaskMonitor monitor, MessageLog log)
protected void load(Program program, ImporterSettings settings)
throws CancelledException, IOException {
this.log = log;
parseDumpFile(provider, program, options, loadSpec, monitor);
this.log = settings.log();
parseDumpFile(settings.provider(), program, settings.options(), settings.loadSpec(),
settings.monitor());
}
private void parseDumpFile(ByteProvider provider, Program program, List<Option> options,
@ -300,7 +299,7 @@ public class DumpFileLoader extends AbstractProgramWrapperLoader {
@Override
public List<Option> getDefaultOptions(ByteProvider provider, LoadSpec loadSpec,
DomainObject domainObject, boolean isLoadIntoProgram) {
DomainObject domainObject, boolean isLoadIntoProgram, boolean mirrorFsLayout) {
List<Option> options = new ArrayList<>();
try {
int size = loadSpec.getLanguageCompilerSpec().getLanguage().getDefaultSpace().getSize();

View file

@ -22,6 +22,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.file.formats.dump.DumpFile;
import ghidra.file.formats.dump.DumpFileReader;
import ghidra.framework.store.LockException;
@ -79,6 +80,7 @@ public class Apport extends DumpFile {
private void createBlocksFromElf(LoadSpec loadSpec, TaskMonitor monitor)
throws IOException, CancelledException {
Object consumer = new Object();
try (
DecodedProvider provider =
new DecodedProvider(this, reader.getByteProvider(), monitor)) {
@ -86,7 +88,13 @@ public class Apport extends DumpFile {
Option base = new Option(ElfLoaderOptionsFactory.IMAGE_BASE_OPTION_NAME,
Long.toHexString(header.getMemoryInfo(0).getBaseAddress()));
options.add(base);
elfLoader.load(provider, loadSpec, options, program, monitor, log);
program.addConsumer(consumer);
ImporterSettings settings = new ImporterSettings(provider, program.getName(), null,
null, false, loadSpec, options, consumer, log, monitor);
elfLoader.load(program, settings);
}
finally {
program.release(consumer);
}
Memory memory = program.getMemory();

View file

@ -62,12 +62,17 @@ public class DumpPeShim extends PeLoader {
return;
}
program.setEffectiveImageBase(minAddress);
Object consumer = new Object();
try {
load(provider, loadSpec, options, program, monitor, log);
program.addConsumer(consumer);
ImporterSettings settings = new ImporterSettings(provider, program.getName(), null,
null, false, loadSpec, options, consumer, log, monitor);
load(program, settings);
monitor.checkCancelled();
}
finally {
program.setEffectiveImageBase(null);
program.release(consumer);
}
shiftModule();

View file

@ -30,7 +30,6 @@ import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.importer.MessageLog;
import ghidra.app.util.opinion.*;
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;
@ -174,32 +173,27 @@ public class SarifLoader extends AbstractProgramLoader {
}
@Override
protected List<Loaded<Program>> loadProgram(ByteProvider provider, String programName,
Project project, String programFolderPath, LoadSpec loadSpec, List<Option> options,
MessageLog log, Object consumer, TaskMonitor monitor)
protected List<Loaded<Program>> loadProgram(ImporterSettings settings)
throws IOException, LoadException, CancelledException {
//throw new RuntimeException("SARIF importer supports only 'Add To Program'");
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, log);
ParseResult result = parse(settings.provider(), settings.log());
Address imageBase = null;
if (result.lastInfo.imageBase != null) {
imageBase = importerLanguage.getAddressFactory().getAddress(result.lastInfo.imageBase);
}
Program prog = createProgram(provider, programName, imageBase, getName(), importerLanguage,
importerCompilerSpec, consumer);
List<Loaded<Program>> loadedList =
List.of(new Loaded<>(prog, programName, project, programFolderPath, consumer));
Program prog = createProgram(imageBase, settings);
List<Loaded<Program>> loadedList = List.of(new Loaded<>(prog, settings));
boolean success = false;
try {
success = doImport(result.lastSarifMgr, options, log, prog, monitor, false);
success = doImport(result.lastSarifMgr, 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");
@ -212,11 +206,11 @@ public class SarifLoader extends AbstractProgramLoader {
}
@Override
protected void loadProgramInto(ByteProvider provider, LoadSpec loadSpec,
List<Option> options, MessageLog log, Program prog, TaskMonitor monitor)
protected void loadProgramInto(Program prog, ImporterSettings settings)
throws IOException, LoadException, CancelledException {
File file = provider.getFile();
doImport(new ProgramSarifMgr(prog, file, log), options, log, prog, monitor, true);
File file = settings.provider().getFile();
doImport(new ProgramSarifMgr(prog, file, settings.log()), settings.options(),
settings.log(), prog, settings.monitor(), true);
}
private boolean doImportWork(final ProgramSarifMgr mgr, final List<Option> options,
@ -313,7 +307,7 @@ public class SarifLoader extends AbstractProgramLoader {
@Override
public List<Option> getDefaultOptions(ByteProvider provider, LoadSpec loadSpec,
DomainObject domainObject, boolean loadIntoProgram) {
DomainObject domainObject, boolean loadIntoProgram, boolean mirrorFsLayout) {
return new SarifProgramOptions().getOptions(loadIntoProgram);
}
@ -323,7 +317,8 @@ public class SarifLoader extends AbstractProgramLoader {
}
@Override
public String validateOptions(ByteProvider provider, LoadSpec loadSpec, List<Option> options, Program program) {
public String validateOptions(ByteProvider provider, LoadSpec loadSpec, List<Option> options,
Program program) {
try {
new SarifProgramOptions().setOptions(options);
}

View file

@ -20,10 +20,8 @@ import java.math.BigInteger;
import java.util.*;
import ghidra.app.cmd.register.SetRegisterCmd;
import ghidra.app.util.Option;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.importer.MessageLog;
import ghidra.framework.store.LockException;
import ghidra.javaclass.format.*;
import ghidra.javaclass.format.attributes.CodeAttribute;
@ -89,10 +87,9 @@ public class JavaLoader extends AbstractProgramWrapperLoader {
}
@Override
public void load(ByteProvider provider, LoadSpec loadSpec, List<Option> options,
Program program, TaskMonitor monitor, MessageLog log) throws IOException {
public void load(Program program, ImporterSettings settings) throws IOException {
try {
doLoad(provider, program, monitor);
doLoad(settings.provider(), program, settings.monitor());
}
catch (LockException e) {
e.printStackTrace();
@ -111,11 +108,6 @@ public class JavaLoader extends AbstractProgramWrapperLoader {
}
}
public void load(ByteProvider provider, Program program, TaskMonitor monitor)
throws IOException {
load(provider, null, null, program, monitor, null);
}
private void doLoad(ByteProvider provider, Program program, TaskMonitor monitor)
throws LockException, MemoryConflictException, AddressOverflowException,
CancelledException, DuplicateNameException, IOException {

View file

@ -66,6 +66,7 @@ for common use cases.
[<a href="#-scriptlog-path-to-script-log-file">-scriptlog &lt;path to script log file&gt;</a>]
[<a href="#-log-path-to-log-file">-log &lt;path to log file&gt;</a>]
[<a href="#-overwrite">-overwrite</a>]
[<a href="#-mirror">-overwrite</a>]
[<a href="#-recursive-depth">-recursive [&lt;depth&gt;]</a>]
[<a href="#-readonly">-readOnly</a>]
[<a href="#-deleteproject">-deleteProject</a>]
@ -254,6 +255,10 @@ contained within a version repository, and the [`-commit`][commit] option has no
the overwrite will fail. Removing a versioned file is also subject to other permission and in-use
restrictions which could also cause an overwrite failure.
### `-mirror`
Applies to [-import][import] mode only. If present, the absolute filesystem path of each imported
file is mirrored in the project, rooted at the specified [folder path][projectname].
### `-recursive [<depth>]`
If present, enables recursive descent into directories and project sub-folders when a directory/
folder has been specified in [`-import`][import] or [`-process`][process] modes.

View file

@ -80,7 +80,7 @@ public class ImporterPluginScreenShots extends GhidraScreenShotGenerator {
loadSpecs.add(new LoadSpec(peLoader, 0,
new LanguageCompilerSpecPair("x86:LE:32:default", "gcc"), false));
loadSpecs.add(new LoadSpec(peLoader, 0,
new LanguageCompilerSpecPair("x86:LE:32:default", "borland"), false));
new LanguageCompilerSpecPair("x86:LE:32:default", "borlandcpp"), false));
loadSpecs.add(new LoadSpec(peLoader, 0,
new LanguageCompilerSpecPair("x86:LE:32:System Management Mode", "default"), false));
runSwing(() -> {

View file

@ -38,6 +38,7 @@ import ghidra.program.model.listing.CodeUnit;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.Memory;
import ghidra.program.model.mem.MemoryAccessException;
import ghidra.util.InvalidNameException;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.VersionException;
import ghidra.util.task.TaskMonitor;
@ -300,7 +301,8 @@ public class ReferencesPluginScreenShots extends GhidraScreenShotGenerator {
}
private void importFile(File file) throws CancelledException, VersionException, IOException {
private void importFile(File file)
throws CancelledException, VersionException, IOException, InvalidNameException {
Project project = env.getProject();
try (LoadResults<Program> loadResults = ProgramLoader.builder()
.source(file)

View file

@ -20,13 +20,11 @@ import java.util.*;
import ghidra.app.util.Option;
import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.importer.MessageLog;
import ghidra.app.util.opinion.AbstractProgramWrapperLoader;
import ghidra.app.util.opinion.LoadSpec;
import ghidra.framework.model.DomainObject;
import ghidra.program.model.listing.Program;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
/**
* Provide class-level documentation that describes what this loader does.
@ -52,18 +50,17 @@ public class SkeletonLoader extends AbstractProgramWrapperLoader {
}
@Override
protected void load(ByteProvider provider, LoadSpec loadSpec, List<Option> options,
Program program, TaskMonitor monitor, MessageLog log)
protected void load(Program prgram, ImporterSettings settiings)
throws CancelledException, IOException {
// Load the bytes from 'provider' into the 'program'.
// Load the bytes from 'settings.provider()' into the 'program'.
}
@Override
public List<Option> getDefaultOptions(ByteProvider provider, LoadSpec loadSpec,
DomainObject domainObject, boolean isLoadIntoProgram) {
List<Option> list =
super.getDefaultOptions(provider, loadSpec, domainObject, isLoadIntoProgram);
DomainObject domainObject, boolean isLoadIntoProgram, boolean mirrorFsLayout) {
List<Option> list = super.getDefaultOptions(provider, loadSpec, domainObject,
isLoadIntoProgram, mirrorFsLayout);
// If this loader has custom options, add them to 'list'
list.add(new Option("Option name goes here", "Default option value goes here"));
@ -72,7 +69,8 @@ public class SkeletonLoader extends AbstractProgramWrapperLoader {
}
@Override
public String validateOptions(ByteProvider provider, LoadSpec loadSpec, List<Option> options, Program program) {
public String validateOptions(ByteProvider provider, LoadSpec loadSpec, List<Option> options,
Program program) {
// If this loader has custom options, validate them here. Not all options require
// validation.