GP-2877: Refactoring Loader and AutoImporter to better accommodate loading more than one thing

This commit is contained in:
Ryan Kurtz 2023-02-13 16:58:30 -05:00
parent 65e2c720b4
commit 1574262722
40 changed files with 2049 additions and 1300 deletions

View file

@ -22,11 +22,20 @@
// list. // list.
// //
//@category Symbol //@category Symbol
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import ghidra.app.script.GhidraScript; import ghidra.app.script.GhidraScript;
import ghidra.app.util.importer.MessageLog; import ghidra.app.util.importer.MessageLog;
import ghidra.app.util.opinion.ElfLoader; import ghidra.app.util.opinion.ElfLoader;
import ghidra.app.util.opinion.Loaded;
import ghidra.framework.model.*;
import ghidra.program.model.listing.Library;
import ghidra.program.model.listing.Program;
import ghidra.program.util.ELFExternalSymbolResolver; import ghidra.program.util.ELFExternalSymbolResolver;
import ghidra.util.Msg; import ghidra.util.Msg;
import ghidra.util.exception.VersionException;
public class FixupELFExternalSymbolsScript extends GhidraScript { public class FixupELFExternalSymbolsScript extends GhidraScript {
@ -38,10 +47,62 @@ public class FixupELFExternalSymbolsScript extends GhidraScript {
")"); ")");
return; return;
} }
MessageLog msgLog = new MessageLog(); MessageLog messageLog = new MessageLog();
ELFExternalSymbolResolver.fixUnresolvedExternalSymbols(currentProgram, false, msgLog, Object consumer = new Object();
monitor); ProjectData projectData = currentProgram.getDomainFile().getParent().getProjectData();
Msg.info(this, msgLog.toString()); List<Loaded<Program>> loadedPrograms = new ArrayList<>();
// Add current program to list
loadedPrograms.add(new Loaded<>(currentProgram, currentProgram.getName(),
currentProgram.getDomainFile().getPathname()));
// Add external libraries to list
for (Library extLibrary : ELFExternalSymbolResolver.getLibrarySearchList(currentProgram)) {
monitor.checkCanceled();
String libName = extLibrary.getName();
String libPath = extLibrary.getAssociatedProgramPath();
if (libPath == null) {
continue;
}
DomainFile libDomainFile = projectData.getFile(libPath);
if (libDomainFile == null) {
messageLog.appendMsg("Referenced external program not found: " + libPath);
continue;
}
DomainObject libDomainObject = null;
try {
libDomainObject =
libDomainFile.getDomainObject(consumer, false, false, monitor);
if (libDomainObject instanceof Program program) {
loadedPrograms.add(new Loaded<>(program, libName, libPath));
}
else {
messageLog
.appendMsg("Referenced external program is not a program: " + libPath);
}
}
catch (IOException e) {
// failed to open library
messageLog.appendMsg("Failed to open library dependency project file: " +
libDomainFile.getPathname());
}
catch (VersionException e) {
messageLog.appendMsg(
"Referenced external program requires updgrade, unable to consider symbols: " +
libPath);
}
}
// Resolve symbols
ELFExternalSymbolResolver.fixUnresolvedExternalSymbols(loadedPrograms, messageLog, monitor);
// Cleanup
for (int i = 1; i < loadedPrograms.size(); i++) {
loadedPrograms.get(i).release(consumer);
}
Msg.info(this, messageLog.toString());
} }
} }

View file

@ -1,6 +1,5 @@
/* ### /* ###
* IP: GHIDRA * IP: GHIDRA
* REVIEWED: YES
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -17,15 +16,17 @@
//Imports all programs from a selected directory. //Imports all programs from a selected directory.
//@category Import //@category Import
import java.io.File;
import java.io.IOException;
import ghidra.app.script.GhidraScript; import ghidra.app.script.GhidraScript;
import ghidra.app.util.importer.AutoImporter; import ghidra.app.util.importer.AutoImporter;
import ghidra.app.util.importer.MessageLog; import ghidra.app.util.importer.MessageLog;
import ghidra.app.util.opinion.LoadResults;
import ghidra.framework.model.DomainFolder; import ghidra.framework.model.DomainFolder;
import ghidra.program.model.lang.LanguageCompilerSpecPair; import ghidra.program.model.lang.LanguageCompilerSpecPair;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
import java.io.File;
public class ImportAllProgramsFromADirectoryScript extends GhidraScript { public class ImportAllProgramsFromADirectoryScript extends GhidraScript {
@Override @Override
@ -52,36 +53,23 @@ public class ImportAllProgramsFromADirectoryScript extends GhidraScript {
continue; continue;
} }
Program program = null; LoadResults<Program> loadResults = null;
try { try {
program = importFile(file); loadResults = AutoImporter.importByLookingForLcs(file, state.getProject(),
folder.getPathname(), language.getLanguage(), language.getCompilerSpec(), this,
log, monitor);
loadResults.getPrimary().save(state.getProject(), log, monitor);
} }
catch (Exception e) { catch (IOException e) {
e.printStackTrace();
}
if (program == null) {
try {
program =
AutoImporter.importByLookingForLcs(file, folder, language.getLanguage(),
language.getCompilerSpec(), this, log, monitor);
}
catch (Exception e) {
e.printStackTrace();
}
}
if (program == null) {
println("Unable to import program from file " + file.getName()); println("Unable to import program from file " + file.getName());
} }
else { finally {
//openProgram( program ); if (loadResults != null) {
program.release(this); loadResults.release(this);
}
} }
println(log.toString()); println(log.toString());
log.clear(); log.clear();
} }
} }

View file

@ -39,6 +39,7 @@ import ghidra.app.util.demangler.DemanglerUtil;
import ghidra.app.util.dialog.AskAddrDialog; import ghidra.app.util.dialog.AskAddrDialog;
import ghidra.app.util.importer.AutoImporter; import ghidra.app.util.importer.AutoImporter;
import ghidra.app.util.importer.MessageLog; import ghidra.app.util.importer.MessageLog;
import ghidra.app.util.opinion.*;
import ghidra.app.util.query.TableService; import ghidra.app.util.query.TableService;
import ghidra.app.util.viewer.field.BrowserCodeUnitFormat; import ghidra.app.util.viewer.field.BrowserCodeUnitFormat;
import ghidra.app.util.viewer.field.CommentUtils; import ghidra.app.util.viewer.field.CommentUtils;
@ -3382,18 +3383,38 @@ public abstract class GhidraScript extends FlatProgramAPI {
/** /**
* Attempts to import the specified file. It attempts to detect the format and * Attempts to import the specified file. It attempts to detect the format and
* automatically import the file. If the format is unable to be determined, then * automatically import the file. If the format is unable to be determined, then
* null is returned. * null is returned. For more control over the import process, {@link AutoImporter} may be
* directly called.
* <p>
* NOTE: The returned {@link Program} is not automatically saved into the current project.
* <p>
* NOTE: It is the responsibility of the script that calls this method to release the returned
* {@link Program} with {@link DomainObject#release(Object consumer)} when it is no longer
* needed, where <code>consumer</code> is <code>this</code>.
* *
* @param file the file to import * @param file the file to import
* @return the newly imported program, or null * @return the newly imported program, or null
* @throws Exception if any exceptions occur while importing * @throws Exception if any exceptions occur while importing
*/ */
public Program importFile(File file) throws Exception { public Program importFile(File file) throws Exception {
return AutoImporter.importByUsingBestGuess(file, null, this, new MessageLog(), monitor); try {
LoadResults<Program> loadResults = AutoImporter.importByUsingBestGuess(file,
state.getProject(), null, this, new MessageLog(), monitor);
loadResults.releaseNonPrimary(this);
return loadResults.getPrimaryDomainObject();
}
catch (LoadException e) {
return null;
}
} }
/** /**
* Imports the specified file as raw binary. * Imports the specified file as raw binary. For more control over the import process,
* {@link AutoImporter} may be directly called.
* <p>
* NOTE: It is the responsibility of the script that calls this method to release the returned
* {@link Program} with {@link DomainObject#release(Object consumer)} when it is no longer
* needed, where <code>consumer</code> is <code>this</code>.
* *
* @param file the file to import * @param file the file to import
* @param language the language of the new program * @param language the language of the new program
@ -3403,8 +3424,14 @@ public abstract class GhidraScript extends FlatProgramAPI {
*/ */
public Program importFileAsBinary(File file, Language language, CompilerSpec compilerSpec) public Program importFileAsBinary(File file, Language language, CompilerSpec compilerSpec)
throws Exception { throws Exception {
return AutoImporter.importAsBinary(file, null, language, compilerSpec, this, try {
new MessageLog(), monitor); Loaded<Program> loaded = AutoImporter.importAsBinary(file, state.getProject(), null,
language, compilerSpec, this, new MessageLog(), monitor);
return loaded.getDomainObject();
}
catch (LoadException e) {
return null;
}
} }
/** /**

View file

@ -21,8 +21,7 @@ import java.util.StringTokenizer;
import ghidra.app.util.importer.AutoImporter; import ghidra.app.util.importer.AutoImporter;
import ghidra.app.util.importer.MessageLog; import ghidra.app.util.importer.MessageLog;
import ghidra.app.util.opinion.Loader; import ghidra.app.util.opinion.*;
import ghidra.app.util.opinion.PeLoader;
import ghidra.framework.main.AppInfo; import ghidra.framework.main.AppInfo;
import ghidra.framework.model.*; import ghidra.framework.model.*;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
@ -128,21 +127,25 @@ public abstract class ProgramCoordinator {
MessageLog messageLog = new MessageLog(); MessageLog messageLog = new MessageLog();
DomainFolder folder = getFolder(file.getParent()); DomainFolder folder = getFolder(file.getParent());
Class<? extends Loader> loaderClass = PeLoader.class; Class<? extends Loader> loaderClass = PeLoader.class;
LoadResults<Program> loadResults = null;
try { try {
Language language = languageService.getDefaultLanguage( Language language = languageService.getDefaultLanguage(
Processor.findOrPossiblyCreateProcessor("x86")); Processor.findOrPossiblyCreateProcessor("x86"));
CompilerSpec compilerSpec = CompilerSpec compilerSpec =
language.getCompilerSpecByID(new CompilerSpecID("windows")); language.getCompilerSpecByID(new CompilerSpecID("windows"));
importProgram = AutoImporter.importByUsingSpecificLoaderClassAndLcs(file, loadResults = AutoImporter.importByUsingSpecificLoaderClassAndLcs(file,
folder, loaderClass, null, language, compilerSpec, consumer, messageLog, AppInfo.getActiveProject(), folder.getPathname(), loaderClass, null,
monitor); language, compilerSpec, consumer, messageLog, monitor);
importProgram = loadResults.getPrimaryDomainObject();
programManager.openProgram(importProgram); programManager.openProgram(importProgram);
importProgram.release(this);
} }
catch (Exception e) { catch (Exception e) {
e.printStackTrace();//TODO e.printStackTrace();//TODO
} }
finally { finally {
if (loadResults != null) {
loadResults.release(this);
}
importSemaphore.notify(); importSemaphore.notify();
} }
} }

View file

@ -190,6 +190,14 @@ public class Option {
return false; return false;
} }
} }
else if (Integer.class.isAssignableFrom(getValueClass())) {
try {
setValue(Integer.decode(str));
}
catch (NumberFormatException e) {
return false;
}
}
else if (Address.class.isAssignableFrom(getValueClass())) { else if (Address.class.isAssignableFrom(getValueClass())) {
try { try {
Address origAddr = (Address) getValue(); Address origAddr = (Address) getValue();

View file

@ -33,10 +33,12 @@ import docking.widgets.checkbox.GCheckBox;
import docking.widgets.combobox.GComboBox; import docking.widgets.combobox.GComboBox;
import docking.widgets.label.GLabel; import docking.widgets.label.GLabel;
import docking.widgets.textfield.IntegerTextField; import docking.widgets.textfield.IntegerTextField;
import ghidra.app.util.opinion.AbstractLibrarySupportLoader; import ghidra.app.util.opinion.*;
import ghidra.app.util.opinion.LibraryPathsDialog; import ghidra.framework.main.AppInfo;
import ghidra.framework.main.DataTreeDialog; import ghidra.framework.main.DataTreeDialog;
import ghidra.framework.model.DomainFolder; import ghidra.framework.model.DomainFolder;
import ghidra.framework.model.Project;
import ghidra.framework.options.SaveState;
import ghidra.program.model.address.*; import ghidra.program.model.address.*;
import ghidra.util.exception.AssertException; import ghidra.util.exception.AssertException;
import ghidra.util.layout.*; import ghidra.util.layout.*;
@ -251,21 +253,35 @@ public class OptionsEditorPanel extends JPanel {
} }
private Component buildProjectFolderEditor(Option option) { private Component buildProjectFolderEditor(Option option) {
JPanel panel = new JPanel(new BorderLayout()); Project project = AppInfo.getActiveProject();
JTextField textField = new JTextField(); final SaveState state;
SaveState existingState = project.getSaveableData(Loader.OPTIONS_PROJECT_SAVE_STATE_KEY);
if (existingState != null) {
state = existingState;
}
else {
state = new SaveState();
project.setSaveableData(Loader.OPTIONS_PROJECT_SAVE_STATE_KEY, state);
}
String lastFolderPath = state.getString(option.getName(), "");
option.setValue(lastFolderPath);
JTextField textField = new JTextField(lastFolderPath);
textField.setEditable(false); textField.setEditable(false);
JButton button = new BrowseButton(); JButton button = new BrowseButton();
button.addActionListener(e -> { button.addActionListener(e -> {
DataTreeDialog dataTreeDialog = DataTreeDialog dataTreeDialog =
new DataTreeDialog(this, "Choose a project folder", DataTreeDialog.CHOOSE_FOLDER); new DataTreeDialog(this, "Choose a project folder", DataTreeDialog.CHOOSE_FOLDER);
dataTreeDialog.setSelectedFolder(null); dataTreeDialog.setSelectedFolder(project.getProjectData().getFolder(lastFolderPath));
dataTreeDialog.showComponent(); dataTreeDialog.showComponent();
DomainFolder folder = dataTreeDialog.getDomainFolder(); DomainFolder folder = dataTreeDialog.getDomainFolder();
if (folder != null) { if (folder != null) {
textField.setText(folder.getPathname()); String newFolderPath = folder.getPathname();
option.setValue(folder.getPathname()); textField.setText(newFolderPath);
option.setValue(newFolderPath);
state.putString(option.getName(), newFolderPath);
} }
}); });
JPanel panel = new JPanel(new BorderLayout());
panel.add(textField, BorderLayout.CENTER); panel.add(textField, BorderLayout.CENTER);
panel.add(button, BorderLayout.EAST); panel.add(button, BorderLayout.EAST);
return panel; return panel;

View file

@ -31,7 +31,7 @@ import ghidra.app.script.*;
import ghidra.app.util.headless.HeadlessScript.HeadlessContinuationOption; import ghidra.app.util.headless.HeadlessScript.HeadlessContinuationOption;
import ghidra.app.util.importer.AutoImporter; import ghidra.app.util.importer.AutoImporter;
import ghidra.app.util.importer.MessageLog; import ghidra.app.util.importer.MessageLog;
import ghidra.app.util.opinion.BinaryLoader; import ghidra.app.util.opinion.*;
import ghidra.framework.*; import ghidra.framework.*;
import ghidra.framework.client.ClientUtil; import ghidra.framework.client.ClientUtil;
import ghidra.framework.client.RepositoryAdapter; import ghidra.framework.client.RepositoryAdapter;
@ -1382,7 +1382,15 @@ public class HeadlessAnalyzer {
return p; return p;
} }
private boolean checkOverwrite(DomainFile df) throws IOException { private boolean checkOverwrite(Loaded<Program> loaded) throws IOException {
DomainFolder folder = project.getProjectData().getFolder(loaded.getProjectFolderPath());
if (folder == null) {
return true;
}
DomainFile df = folder.getFile(loaded.getName());
if (df == null) {
return true;
}
if (options.overwrite) { if (options.overwrite) {
try { try {
if (df.isHijacked()) { if (df.isHijacked()) {
@ -1500,192 +1508,148 @@ public class HeadlessAnalyzer {
Msg.info(this, "IMPORTING: " + file.getAbsolutePath()); Msg.info(this, "IMPORTING: " + file.getAbsolutePath());
Program program = null; LoadResults<Program> loadResults = null;
Loaded<Program> primary = null;
try { try {
String dfName = null;
DomainFile df = null;
DomainFolder domainFolder = null;
try {
// Gets parent folder for import (creates path if doesn't exist)
domainFolder = getDomainFolder(folderPath, false);
dfName = file.getName(); // Perform the load. Note that loading 1 file may result in more than 1 thing getting
// loaded.
loadResults = loadPrograms(file, folderPath);
Msg.info(this, "IMPORTING: Loaded " + (loadResults.size() - 1) + " additional files");
if (dfName.toLowerCase().endsWith(".gzf") || primary = loadResults.getPrimary();
dfName.toLowerCase().endsWith(".xml")) { Program primaryProgram = primary.getDomainObject();
// Use filename without .gzf
int index = dfName.lastIndexOf('.');
dfName = dfName.substring(0, index);
}
// Make sure we are allowed to save ALL programs to the project. If not, save none and
// fail.
if (!options.readOnly) { if (!options.readOnly) {
if (domainFolder != null) { for (Loaded<Program> loaded : loadResults) {
df = domainFolder.getFile(dfName); if (!checkOverwrite(loaded)) {
}
if (df != null && !checkOverwrite(df)) {
return false;
}
df = null;
}
program = loadProgram(file);
if (program == null) {
return false;
}
// Check if there are defined memory blocks; abort if not (there is nothing
// to work with!)
if (program.getMemory().getAllInitializedAddressSet().isEmpty()) {
Msg.error(this, "REPORT: Error: No memory blocks were defined for file '" +
file.getAbsolutePath() + "'.");
return false; return false;
} }
} }
catch (Exception exc) { }
Msg.error(this, "REPORT: " + exc.getMessage(), exc);
exc.printStackTrace(); // Check if there are defined memory blocks in the primary program.
// Abort if not (there is nothing to work with!).
if (primaryProgram.getMemory().getAllInitializedAddressSet().isEmpty()) {
Msg.error(this, "REPORT: Error: No memory blocks were defined for file " +
file.getAbsolutePath());
return false; return false;
} }
Msg.info(this, // Analyze the primary program, and determine if we should save.
"REPORT: Import succeeded with language \"" + // TODO: Analyze non-primary programs (GP-2965).
program.getLanguageID().getIdAsString() + "\" and cspec \"" + boolean doSave =
program.getCompilerSpec().getCompilerSpecID().getIdAsString() + analyzeProgram(file.getAbsolutePath(), primaryProgram) && !options.readOnly;
"\" for file: " + file.getAbsolutePath());
boolean doSave;
try {
doSave = analyzeProgram(file.getAbsolutePath(), program) && !options.readOnly;
if (!doSave) {
program.setTemporary(true);
}
// The act of marking the program as temporary by a script will signal // The act of marking the program as temporary by a script will signal
// us to discard any program changes. // us to discard any changes
if (program.isTemporary()) { if (!doSave) {
if (options.readOnly) { loadResults.forEach(e -> e.getDomainObject().setTemporary(true));
Msg.info(this, "REPORT: Discarded file import due to readOnly option: " +
file.getAbsolutePath());
}
else {
Msg.info(this, "REPORT: Discarded file import as a result of script " +
"activity or analysis timeout: " + file.getAbsolutePath());
}
return true;
} }
try { // Apply saveDomainFolder to the primary program, if applicable.
// We don't support changing the save folder on any non-primary loaded programs.
// Note that saveDomainFolder is set by pre/post-scripts, so it can only be used
// after analysis happens.
if (saveDomainFolder != null) { if (saveDomainFolder != null) {
primary.setProjectFolderPath(saveDomainFolder.getPathname());
df = saveDomainFolder.getFile(dfName); if (!checkOverwrite(primary)) {
// Return if file already exists and overwrite == false
if (df != null && !checkOverwrite(df)) {
return false; return false;
} }
domainFolder = saveDomainFolder;
} }
else if (domainFolder == null) {
domainFolder = getDomainFolder(folderPath, true);
}
df = domainFolder.createFile(dfName, program, TaskMonitor.DUMMY);
Msg.info(this, "REPORT: Save succeeded for file: " + df.getPathname());
if (options.commit) { // Save
for (Loaded<Program> loaded : loadResults) {
AutoAnalysisManager.getAnalysisManager(program).dispose(); if (!loaded.getDomainObject().isTemporary()) {
program.release(this); try {
program = null; DomainFile domainFile =
loaded.save(project, new MessageLog(), TaskMonitor.DUMMY);
commitProgram(df); Msg.info(this, String.format("REPORT: Save succeeded for: %s (%s)", loaded,
} domainFile));
} }
catch (IOException e) { catch (IOException e) {
e.printStackTrace(); Msg.info(this, "REPORT: Save failed for: " + loaded);
throw new IOException("Cannot create file: " + domainFolder.getPathname() +
DomainFolder.SEPARATOR + dfName, e);
} }
} }
catch (Exception exc) { else {
String logErrorMsg = if (options.readOnly) {
file.getAbsolutePath() + " Error during analysis: " + exc.getMessage(); Msg.info(this, "REPORT: Discarded file import due to readOnly option: " +
Msg.info(this, logErrorMsg); loaded);
return false; }
else {
Msg.info(this,
"REPORT: Discarded file import as a result of script " +
"activity or analysis timeout: " + loaded);
} }
finally {
if (program != null) {
AutoAnalysisManager.getAnalysisManager(program).dispose();
} }
} }
// Commit changes
if (options.commit) {
for (Loaded<Program> loaded : loadResults) {
if (!loaded.getDomainObject().isTemporary()) {
if (loaded == primary) {
AutoAnalysisManager.getAnalysisManager(primaryProgram).dispose();
}
loaded.release(this);
commitProgram(loaded.getSavedDomainFile());
}
}
}
Msg.info(this, "REPORT: Import succeeded");
return true; return true;
} }
finally { catch (LoadException e) {
// Program must be released here, since the AutoAnalysisManager uses program to
// call dispose() in the finally() block above.
if (program != null) {
program.release(this);
program = null;
}
}
}
private Program loadProgram(File file) throws VersionException, InvalidNameException,
DuplicateNameException, CancelledException, IOException {
MessageLog messageLog = new MessageLog();
Program program = null;
// NOTE: we must pass a null DomainFolder to the AutoImporter so as not to
// allow the DomainFile to be saved at this point. DomainFile should be
// saved after all applicable analysis/scripts are run.
if (options.loaderClass == null) {
// User did not specify a loader
if (options.language == null) {
program = AutoImporter.importByUsingBestGuess(file, null, this, messageLog,
TaskMonitor.DUMMY);
}
else {
program = AutoImporter.importByLookingForLcs(file, null, options.language,
options.compilerSpec, this, messageLog, TaskMonitor.DUMMY);
}
}
else {
// User specified a loader
if (options.language == null) {
program = AutoImporter.importByUsingSpecificLoaderClass(file, null,
options.loaderClass, options.loaderArgs, this, messageLog, TaskMonitor.DUMMY);
}
else {
program = AutoImporter.importByUsingSpecificLoaderClassAndLcs(file, null,
options.loaderClass, options.loaderArgs, options.language, options.compilerSpec,
this, messageLog, TaskMonitor.DUMMY);
}
}
if (program == null) {
Msg.error(this, "The AutoImporter could not successfully load " + Msg.error(this, "The AutoImporter could not successfully load " +
file.getAbsolutePath() + file.getAbsolutePath() +
" with the provided import parameters. Please ensure that any specified" + " with the provided import parameters. Please ensure that any specified" +
" processor/cspec arguments are compatible with the loader that is used during" + " processor/cspec arguments are compatible with the loader that is used during" +
" import and try again."); " import and try again.");
if (options.loaderClass != null && options.loaderClass != BinaryLoader.class) { if (options.loaderClass != null && options.loaderClass != BinaryLoader.class) {
Msg.error(this, Msg.error(this,
"NOTE: Import failure may be due to missing opinion for \"" + "NOTE: Import failure may be due to missing opinion for \"" +
options.loaderClass.getSimpleName() + options.loaderClass.getSimpleName() +
"\". If so, please contact Ghidra team for assistance."); "\". If so, please contact Ghidra team for assistance.");
} }
return false;
return null; }
catch (Exception e) {
Msg.error(this, "REPORT: " + e.getMessage(), e);
return false;
}
finally {
if (loadResults != null) {
loadResults.release(this);
}
}
} }
return program; private LoadResults<Program> loadPrograms(File file, String folderPath) throws VersionException,
InvalidNameException, DuplicateNameException, CancelledException, IOException,
LoadException {
MessageLog messageLog = new MessageLog();
if (options.loaderClass == null) {
// User did not specify a loader
if (options.language == null) {
return AutoImporter.importByUsingBestGuess(file, project, folderPath, this,
messageLog, TaskMonitor.DUMMY);
}
return AutoImporter.importByLookingForLcs(file, project, folderPath, options.language,
options.compilerSpec, this, messageLog, TaskMonitor.DUMMY);
}
// User specified a loader
if (options.language == null) {
return AutoImporter.importByUsingSpecificLoaderClass(file, project, folderPath,
options.loaderClass, options.loaderArgs, this, messageLog, TaskMonitor.DUMMY);
}
return AutoImporter.importByUsingSpecificLoaderClassAndLcs(file, project, folderPath,
options.loaderClass, options.loaderArgs, options.language, options.compilerSpec, this,
messageLog, TaskMonitor.DUMMY);
} }
private void processWithImport(File file, String folderPath, boolean isFirstTime) private void processWithImport(File file, String folderPath, boolean isFirstTime)

View file

@ -17,6 +17,7 @@ package ghidra.app.util.importer;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.AccessMode;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.function.Predicate; import java.util.function.Predicate;
@ -24,10 +25,10 @@ import java.util.function.Predicate;
import generic.stl.Pair; import generic.stl.Pair;
import ghidra.app.util.Option; import ghidra.app.util.Option;
import ghidra.app.util.bin.ByteProvider; import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.bin.RandomAccessByteProvider; import ghidra.app.util.bin.FileByteProvider;
import ghidra.app.util.opinion.*; import ghidra.app.util.opinion.*;
import ghidra.framework.model.DomainFolder; import ghidra.formats.gfilesystem.FileSystemService;
import ghidra.framework.model.DomainObject; import ghidra.framework.model.*;
import ghidra.program.model.address.AddressFactory; import ghidra.program.model.address.AddressFactory;
import ghidra.program.model.lang.*; import ghidra.program.model.lang.*;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
@ -38,146 +39,443 @@ import ghidra.util.exception.*;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
/** /**
* Utility methods to do imports automatically (without requiring user interaction). * Utility methods to do {@link Program} imports automatically (without requiring user interaction)
*/ */
public final class AutoImporter { public final class AutoImporter {
private AutoImporter() { private AutoImporter() {
// service class; cannot instantiate // service class; cannot instantiate
} }
public static Program importByUsingBestGuess(File file, DomainFolder programFolder, /**
Object consumer, MessageLog messageLog, TaskMonitor monitor) throws IOException, * Automatically imports the given {@link File} with the best matching {@link Loader} for the
CancelledException, DuplicateNameException, InvalidNameException, VersionException { * {@link File}'s format.
List<Program> programs = importFresh(file, programFolder, consumer, messageLog, monitor, * <p>
LoaderService.ACCEPT_ALL, LoadSpecChooser.CHOOSE_THE_FIRST_PREFERRED, null, * Note that when the import completes, the returned {@link Loaded} {@link Program}s are not
OptionChooser.DEFAULT_OPTIONS, MultipleProgramsStrategy.ONE_PROGRAM_OR_NULL); * saved to a project. That is the responsibility of the caller (see
if (programs != null && programs.size() == 1) { * {@link LoadResults#save(Project, Object, MessageLog, TaskMonitor)}).
return programs.get(0); * <p>
} * It is also the responsibility of the caller to release the returned {@link Loaded}
return null; * {@link Program}s with {@link LoadResults#release(Object)} when they are no longer needed.
*
* @param file The {@link File} to import
* @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} results
* should be queried for their true project folder paths using
* {@link Loaded#getProjectFolderPath()}.
* @param consumer A consumer
* @param messageLog The log
* @param monitor A task monitor
* @return The {@link LoadResults} which contains one ore more {@link Loaded} {@link Program}s
* (created but not saved)
* @throws IOException if there was an IO-related problem loading
* @throws CancelledException if the operation was cancelled
* @throws DuplicateNameException if the load resulted in a {@link Program} naming conflict
* @throws InvalidNameException if an invalid {@link Program} name was used during load
* @throws VersionException if there was an issue with database versions, probably due to a
* failed language upgrade
* @throws LoadException if nothing was loaded
*/
public static LoadResults<Program> importByUsingBestGuess(File file, Project project,
String projectFolderPath, Object consumer, MessageLog messageLog, TaskMonitor monitor)
throws IOException, CancelledException, DuplicateNameException, InvalidNameException,
VersionException, LoadException {
return importFresh(file, project, projectFolderPath, consumer, messageLog,
monitor, LoaderService.ACCEPT_ALL, LoadSpecChooser.CHOOSE_THE_FIRST_PREFERRED, null,
OptionChooser.DEFAULT_OPTIONS);
} }
public static Program importByUsingBestGuess(ByteProvider provider, DomainFolder programFolder, /**
Object consumer, MessageLog messageLog, TaskMonitor monitor) throws IOException, * Automatically imports the give {@link ByteProvider bytes} with the best matching
CancelledException, DuplicateNameException, InvalidNameException, VersionException { * {@link Loader} for the {@link ByteProvider}'s format.
List<Program> programs = importFresh(provider, programFolder, consumer, messageLog, monitor, * <p>
LoaderService.ACCEPT_ALL, LoadSpecChooser.CHOOSE_THE_FIRST_PREFERRED, null, * Note that when the import completes, the returned {@link Loaded} {@link Program}s are not
OptionChooser.DEFAULT_OPTIONS, MultipleProgramsStrategy.ONE_PROGRAM_OR_NULL); * saved to a project. That is the responsibility of the caller (see
if (programs != null && programs.size() == 1) { * {@link LoadResults#save(Project, Object, MessageLog, TaskMonitor)}).
return programs.get(0); * <p>
} * It is also the responsibility of the caller to release the returned {@link Loaded}
return null; * {@link Program}s with {@link LoadResults#release(Object)} when they are no longer needed.
} *
* @param provider The bytes to import
public static Program importByUsingSpecificLoaderClass(File file, DomainFolder programFolder, * @param project The {@link Project}. Loaders can use this to take advantage of existing
Class<? extends Loader> loaderClass, List<Pair<String, String>> loaderArgs, * {@link DomainFolder}s and {@link DomainFile}s to do custom behaviors such as loading
Object consumer, MessageLog messageLog, TaskMonitor monitor) throws IOException, * libraries. Could be null if there is no project.
CancelledException, DuplicateNameException, InvalidNameException, VersionException { * @param projectFolderPath A suggested project folder path for the {@link Loaded}
SingleLoaderFilter loaderFilter = new SingleLoaderFilter(loaderClass, loaderArgs); * {@link Program}s. This is just a suggestion, and a {@link Loader} implementation
List<Program> programs = importFresh(file, programFolder, consumer, messageLog, monitor, * reserves the right to change it for each {@link Loaded} result. The {@link Loaded} results
loaderFilter, LoadSpecChooser.CHOOSE_THE_FIRST_PREFERRED, null, * should be queried for their true project folder paths using
new LoaderArgsOptionChooser(loaderFilter), * {@link Loaded#getProjectFolderPath()}.
MultipleProgramsStrategy.ONE_PROGRAM_OR_NULL); * @param consumer A consumer
if (programs != null && programs.size() == 1) { * @param messageLog The log
return programs.get(0); * @param monitor A task monitor
} * @return The {@link LoadResults} which contains one ore more {@link Loaded} {@link Program}s
return null; * (created but not saved)
} * @throws IOException if there was an IO-related problem loading
* @throws CancelledException if the operation was cancelled
public static Program importByLookingForLcs(File file, DomainFolder programFolder, * @throws DuplicateNameException if the load resulted in a {@link Program} naming conflict
Language language, CompilerSpec compilerSpec, Object consumer, MessageLog messageLog, * @throws InvalidNameException if an invalid {@link Program} name was used during load
* @throws VersionException if there was an issue with database versions, probably due to a
* failed language upgrade
* @throws LoadException if nothing was loaded
*/
public static LoadResults<Program> importByUsingBestGuess(ByteProvider provider,
Project project, String projectFolderPath, Object consumer, MessageLog messageLog,
TaskMonitor monitor) throws IOException, CancelledException, DuplicateNameException, TaskMonitor monitor) throws IOException, CancelledException, DuplicateNameException,
InvalidNameException, VersionException { InvalidNameException, VersionException, LoadException {
List<Program> programs = importFresh(file, programFolder, consumer, messageLog, monitor, return importFresh(provider, project, projectFolderPath, consumer, messageLog, monitor,
LoaderService.ACCEPT_ALL, new LcsHintLoadSpecChooser(language, compilerSpec), null, LoaderService.ACCEPT_ALL, LoadSpecChooser.CHOOSE_THE_FIRST_PREFERRED, null,
OptionChooser.DEFAULT_OPTIONS, MultipleProgramsStrategy.ONE_PROGRAM_OR_NULL); OptionChooser.DEFAULT_OPTIONS);
if (programs != null && programs.size() == 1) {
return programs.get(0);
}
return null;
} }
public static Program importByUsingSpecificLoaderClassAndLcs(File file, /**
DomainFolder programFolder, Class<? extends Loader> loaderClass, * Automatically imports the given {@link File} with the given type of {@link Loader}.
* <p>
* Note that when the import completes, the returned {@link Loaded} {@link Program}s are not
* saved to a project. That is the responsibility of the caller (see
* {@link LoadResults#save(Project, Object, MessageLog, TaskMonitor)}).
* <p>
* It is also the responsibility of the caller to release the returned {@link Loaded}
* {@link Program}s with {@link LoadResults#release(Object)} when they are no longer needed.
*
* @param file The {@link File} to import
* @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} results
* should be queried for their true project folder paths using
* {@link Loaded#getProjectFolderPath()}.
* @param loaderClass The {@link Loader} class to use
* @param loaderArgs A {@link List} of optional {@link Loader}-specific arguments
* @param consumer A consumer
* @param messageLog The log
* @param monitor A task monitor
* @return The {@link LoadResults} which contains one ore more {@link Loaded} {@link Program}s
* (created but not saved)
* @throws IOException if there was an IO-related problem loading
* @throws CancelledException if the operation was cancelled
* @throws DuplicateNameException if the load resulted in a {@link Program} naming conflict
* @throws InvalidNameException if an invalid {@link Program} name was used during load
* @throws VersionException if there was an issue with database versions, probably due to a
* failed language upgrade
* @throws LoadException if nothing was loaded
*/
public static LoadResults<Program> importByUsingSpecificLoaderClass(File file,
Project project, String projectFolderPath, Class<? extends Loader> loaderClass,
List<Pair<String, String>> loaderArgs, Object consumer, MessageLog messageLog,
TaskMonitor monitor) throws IOException, CancelledException, DuplicateNameException,
InvalidNameException, VersionException, LoadException {
SingleLoaderFilter loaderFilter = new SingleLoaderFilter(loaderClass, loaderArgs);
return importFresh(file, project, projectFolderPath, consumer, messageLog,
monitor, loaderFilter, LoadSpecChooser.CHOOSE_THE_FIRST_PREFERRED, null,
new LoaderArgsOptionChooser(loaderFilter));
}
/**
* Automatically imports the given {@link File} with the best matching {@link Loader} that
* supports the given language and compiler specification.
* <p>
* Note that when the import completes, the returned {@link Loaded} {@link Program}s are not
* saved to a project. That is the responsibility of the caller (see
* {@link LoadResults#save(Project, Object, MessageLog, TaskMonitor)}).
* <p>
* It is also the responsibility of the caller to release the returned {@link Loaded}
* {@link Program}s with {@link LoadResults#release(Object)} when they are no longer needed.
*
* @param file The {@link File} to import
* @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} results
* should be queried for their true project folder paths using
* {@link Loaded#getProjectFolderPath()}.
* @param language The desired {@link Language}
* @param compilerSpec The desired {@link CompilerSpec compiler specification}
* @param consumer A consumer
* @param messageLog The log
* @param monitor A task monitor
* @return The {@link LoadResults} which contains one ore more {@link Loaded} {@link Program}s
* (created but not saved)
* @throws IOException if there was an IO-related problem loading
* @throws CancelledException if the operation was cancelled
* @throws DuplicateNameException if the load resulted in a {@link Program} naming conflict
* @throws InvalidNameException if an invalid {@link Program} name was used during load
* @throws VersionException if there was an issue with database versions, probably due to a
* failed language upgrade
* @throws LoadException if nothing was loaded
*/
public static LoadResults<Program> importByLookingForLcs(File file, Project project,
String projectFolderPath, Language language, CompilerSpec compilerSpec, Object consumer,
MessageLog messageLog, TaskMonitor monitor) throws IOException, CancelledException,
DuplicateNameException, InvalidNameException, VersionException, LoadException {
return importFresh(file, project, projectFolderPath, consumer, messageLog,
monitor, LoaderService.ACCEPT_ALL, new LcsHintLoadSpecChooser(language, compilerSpec),
null, OptionChooser.DEFAULT_OPTIONS);
}
/**
* Automatically imports the given {@link File} with the given type of {@link Loader}, language,
* and compiler specification.
* <p>
* Note that when the import completes, the returned {@link Loaded} {@link Program}s are not
* saved to a project. That is the responsibility of the caller (see
* {@link LoadResults#save(Project, Object, MessageLog, TaskMonitor)}).
* <p>
* It is also the responsibility of the caller to release the returned {@link Loaded}
* {@link Program}s with {@link LoadResults#release(Object)} when they are no longer needed.
*
* @param file The {@link File} to import
* @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} results
* should be queried for their true project folder paths using
* {@link Loaded#getProjectFolderPath()}.
* @param loaderClass The {@link Loader} class to use
* @param loaderArgs A {@link List} of optional {@link Loader}-specific arguments
* @param language The desired {@link Language}
* @param compilerSpec The desired {@link CompilerSpec compiler specification}
* @param consumer A consumer
* @param messageLog The log
* @param monitor A task monitor
* @return The {@link LoadResults} which contains one ore more {@link Loaded} {@link Program}s
* (created but not saved)
* @throws IOException if there was an IO-related problem loading
* @throws CancelledException if the operation was cancelled
* @throws DuplicateNameException if the load resulted in a {@link Program} naming conflict
* @throws InvalidNameException if an invalid {@link Program} name was used during load
* @throws VersionException if there was an issue with database versions, probably due to a
* failed language upgrade
*/
public static LoadResults<Program> importByUsingSpecificLoaderClassAndLcs(File file,
Project project, String projectFolderPath, Class<? extends Loader> loaderClass,
List<Pair<String, String>> loaderArgs, Language language, CompilerSpec compilerSpec, List<Pair<String, String>> loaderArgs, Language language, CompilerSpec compilerSpec,
Object consumer, MessageLog messageLog, TaskMonitor monitor) throws IOException, Object consumer, MessageLog messageLog, TaskMonitor monitor) throws IOException,
CancelledException, DuplicateNameException, InvalidNameException, VersionException { CancelledException, DuplicateNameException, InvalidNameException, VersionException {
SingleLoaderFilter loaderFilter = new SingleLoaderFilter(loaderClass, loaderArgs); SingleLoaderFilter loaderFilter = new SingleLoaderFilter(loaderClass, loaderArgs);
List<Program> programs = importFresh(file, programFolder, consumer, messageLog, monitor, return importFresh(file, project, projectFolderPath, consumer, messageLog,
loaderFilter, new LcsHintLoadSpecChooser(language, compilerSpec), null, monitor, loaderFilter, new LcsHintLoadSpecChooser(language, compilerSpec), null,
new LoaderArgsOptionChooser(loaderFilter), new LoaderArgsOptionChooser(loaderFilter));
MultipleProgramsStrategy.ONE_PROGRAM_OR_NULL);
if (programs != null && programs.size() == 1) {
return programs.get(0);
}
return null;
} }
private static final Predicate<Loader> BINARY_LOADER = private static final Predicate<Loader> BINARY_LOADER =
new SingleLoaderFilter(BinaryLoader.class); new SingleLoaderFilter(BinaryLoader.class);
public static Program importAsBinary(File file, DomainFolder programFolder, Language language, /**
CompilerSpec compilerSpec, Object consumer, MessageLog messageLog, TaskMonitor monitor) * Automatically imports the given {@link File} with the {@link BinaryLoader}, using the given
throws IOException, CancelledException, DuplicateNameException, InvalidNameException, * language and compiler specification.
VersionException { * <p>
List<Program> programs = importFresh(file, programFolder, consumer, messageLog, monitor, * Note that when the import completes, the returned {@link Loaded} {@link Program} is
BINARY_LOADER, new LcsHintLoadSpecChooser(language, compilerSpec), null, * not saved to a project. That is the responsibility of the caller (see
OptionChooser.DEFAULT_OPTIONS, MultipleProgramsStrategy.ONE_PROGRAM_OR_NULL); * {@link Loaded#save(Project, MessageLog, TaskMonitor)}).
if (programs != null && programs.size() == 1) { * <p>
return programs.get(0); * It is also the responsibility of the caller to release the returned {@link Loaded}
} * {@link Program} with {@link Loaded#release(Object)} when it is no longer needed.
return null; *
* @param file The {@link File} to import
* @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}. This is just a suggestion, and a {@link Loader} implementation
* reserves the right to change it for the {@link Loaded} result. The {@link Loaded} result
* should be queried for its true project folder paths using
* {@link Loaded#getProjectFolderPath()}.
* @param language The desired {@link Language}
* @param compilerSpec The desired {@link CompilerSpec compiler specification}
* @param consumer A consumer
* @param messageLog The log
* @param monitor A task monitor
* @return The {@link Loaded} {@link Program} (created but not saved)
* @throws IOException if there was an IO-related problem loading
* @throws CancelledException if the operation was cancelled
* @throws DuplicateNameException if the load resulted in a {@link Program} naming conflict
* @throws InvalidNameException if an invalid {@link Program} name was used during load
* @throws VersionException if there was an issue with database versions, probably due to a
* failed language upgrade
* @throws LoadException if nothing was loaded
*/
public static Loaded<Program> importAsBinary(File file, Project project,
String projectFolderPath, Language language, CompilerSpec compilerSpec, Object consumer,
MessageLog messageLog, TaskMonitor monitor) throws IOException, CancelledException,
DuplicateNameException, InvalidNameException, VersionException, LoadException {
LoadResults<Program> loadResults = importFresh(file, project, projectFolderPath, consumer,
messageLog, monitor, BINARY_LOADER, new LcsHintLoadSpecChooser(language, compilerSpec),
null, OptionChooser.DEFAULT_OPTIONS);
loadResults.releaseNonPrimary(consumer);
return loadResults.getPrimary();
} }
public static Program importAsBinary(ByteProvider bytes, DomainFolder programFolder, /**
Language language, CompilerSpec compilerSpec, Object consumer, MessageLog messageLog, * Automatically imports the given {@link ByteProvider} bytes with the {@link BinaryLoader},
TaskMonitor monitor) throws IOException, CancelledException, DuplicateNameException, * using the given language and compiler specification.
InvalidNameException, VersionException { * <p>
List<Program> programs = importFresh(bytes, programFolder, consumer, messageLog, monitor, * Note that when the import completes, the returned {@link Loaded} {@link Program} is
BINARY_LOADER, new LcsHintLoadSpecChooser(language, compilerSpec), null, * not saved to a project. That is the responsibility of the caller (see
OptionChooser.DEFAULT_OPTIONS, MultipleProgramsStrategy.ONE_PROGRAM_OR_NULL); * {@link Loaded#save(Project, MessageLog, TaskMonitor)}).
if (programs != null && programs.size() == 1) { * <p>
return programs.get(0); * It is also the responsibility of the caller to release the returned {@link Loaded}
} * {@link Program} with {@link Loaded#release(Object)} when it is no longer needed.
return null; *
* @param bytes The bytes to import
* @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}. This is just a suggestion, and a {@link Loader} implementation
* reserves the right to change it the {@link Loaded} result. The {@link Loaded} result
* should be queried for its true project folder paths using
* {@link Loaded#getProjectFolderPath()}.
* @param language The desired {@link Language}
* @param compilerSpec The desired {@link CompilerSpec compiler specification}
* @param consumer A consumer
* @param messageLog The log
* @param monitor A task monitor
* @return The {@link Loaded} {@link Program} (created but not saved)
* @throws IOException if there was an IO-related problem loading
* @throws CancelledException if the operation was cancelled
* @throws DuplicateNameException if the load resulted in a {@link Program} naming conflict
* @throws InvalidNameException if an invalid {@link Program} name was used during load
* @throws VersionException if there was an issue with database versions, probably due to a
* failed language upgrade
* @throws LoadException if nothing was loaded
*/
public static Loaded<Program> importAsBinary(ByteProvider bytes, Project project,
String projectFolderPath, Language language, CompilerSpec compilerSpec,
Object consumer, MessageLog messageLog, TaskMonitor monitor) throws IOException,
CancelledException, DuplicateNameException, InvalidNameException, VersionException,
LoadException {
LoadResults<Program> loadResults = importFresh(bytes, project, projectFolderPath, consumer,
messageLog, monitor, BINARY_LOADER, new LcsHintLoadSpecChooser(language, compilerSpec),
null, OptionChooser.DEFAULT_OPTIONS);
loadResults.releaseNonPrimary(consumer);
return loadResults.getPrimary();
} }
public static List<Program> importFresh(File file, DomainFolder programFolder, Object consumer, /**
MessageLog messageLog, TaskMonitor monitor, Predicate<Loader> loaderFilter, * Automatically imports the given {@link File} with advanced options.
LoadSpecChooser loadSpecChooser, String programNameOverride, * <p>
OptionChooser optionChooser, MultipleProgramsStrategy multipleProgramsStrategy) * Note that when the import completes, the returned {@link Loaded} {@link Program}s are not
throws IOException, CancelledException, DuplicateNameException, InvalidNameException, * saved to a project. That is the responsibility of the caller (see
VersionException { * {@link LoadResults#save(Project, Object, MessageLog, TaskMonitor)}).
if (file == null) { * <p>
return null; * It is also the responsibility of the caller to release the returned {@link Loaded}
} * {@link Program}s with {@link LoadResults#release(Object)} when they are no longer needed.
*
try (ByteProvider provider = new RandomAccessByteProvider(file)) { * @param file The {@link File} to import
return importFresh(provider, programFolder, consumer, messageLog, monitor, loaderFilter, * @param project The {@link Project}. Loaders can use this to take advantage of existing
loadSpecChooser, programNameOverride, optionChooser, multipleProgramsStrategy); * {@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
public static List<Program> importFresh(ByteProvider provider, DomainFolder programFolder, * reserves the right to change it for each {@link Loaded} result. The {@link Loaded} results
Object consumer, MessageLog messageLog, TaskMonitor monitor, * should be queried for their true project folder paths using
* {@link Loaded#getProjectFolderPath()}.
* @param loaderFilter A {@link Predicate} used to choose what {@link Loader}(s) get used
* @param loadSpecChooser A {@link LoadSpecChooser} used to choose what {@link LoadSpec}(s) get
* used
* @param importNameOverride The name to use for the imported thing. Null to use the
* {@link Loader}'s preferred name.
* @param optionChooser A {@link OptionChooser} used to choose what {@link Loader} options get
* used
* @param consumer A consumer
* @param messageLog The log
* @param monitor A task monitor
* @return The {@link LoadResults} which contains one ore more {@link Loaded} {@link Program}s
* (created but not saved)
* @throws IOException if there was an IO-related problem loading
* @throws CancelledException if the operation was cancelled
* @throws DuplicateNameException if the load resulted in a {@link Program} naming conflict
* @throws InvalidNameException if an invalid {@link Program} name was used during load
* @throws VersionException if there was an issue with database versions, probably due to a
* failed language upgrade
* @throws LoadException if nothing was loaded
*/
public static LoadResults<Program> importFresh(File file, Project project,
String projectFolderPath, Object consumer, MessageLog messageLog, TaskMonitor monitor,
Predicate<Loader> loaderFilter, LoadSpecChooser loadSpecChooser, Predicate<Loader> loaderFilter, LoadSpecChooser loadSpecChooser,
String programNameOverride, OptionChooser optionChooser, String importNameOverride, OptionChooser optionChooser) throws IOException,
MultipleProgramsStrategy multipleProgramsStrategy) throws IOException, CancelledException, DuplicateNameException, InvalidNameException, VersionException,
CancelledException, DuplicateNameException, InvalidNameException, VersionException { LoadException {
if (file == null) {
throw new LoadException("Cannot load null file");
}
try (ByteProvider provider = new FileByteProvider(file,
FileSystemService.getInstance().getLocalFSRL(file), AccessMode.READ)) {
return importFresh(provider, project, projectFolderPath, consumer, messageLog, monitor,
loaderFilter, loadSpecChooser, importNameOverride, optionChooser);
}
}
/**
* Automatically imports the given {@link ByteProvider bytes} with advanced options.
* <p>
* Note that when the import completes, the returned {@link Loaded} {@link Program}s are not
* saved to a project. That is the responsibility of the caller (see
* {@link LoadResults#save(Project, Object, MessageLog, TaskMonitor)}).
* <p>
* It is also the responsibility of the caller to release the returned {@link Loaded}
* {@link Program}s with {@link LoadResults#release(Object)} when they are no longer needed.
*
* @param provider The bytes to import
* @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} results
* should be queried for their true project folder paths using
* {@link Loaded#getProjectFolderPath()}.
* @param loaderFilter A {@link Predicate} used to choose what {@link Loader}(s) get used
* @param loadSpecChooser A {@link LoadSpecChooser} used to choose what {@link LoadSpec}(s) get
* used
* @param importNameOverride The name to use for the imported thing. Null to use the
* {@link Loader}'s preferred name.
* @param optionChooser A {@link OptionChooser} used to choose what {@link Loader} options get
* used
* @param consumer A consumer
* @param messageLog The log
* @param monitor A task monitor
* @return The {@link LoadResults} which contains one ore more {@link Loaded} {@link Program}s
* (created but not saved)
* @throws IOException if there was an IO-related problem loading
* @throws CancelledException if the operation was cancelled
* @throws DuplicateNameException if the load resulted in a {@link Program} naming conflict
* @throws InvalidNameException if an invalid {@link Program} name was used during load
* @throws VersionException if there was an issue with database versions, probably due to a
* failed language upgrade
* @throws LoadException if nothing was loaded
*/
public static LoadResults<Program> importFresh(ByteProvider provider, Project project,
String projectFolderPath, Object consumer, MessageLog messageLog, TaskMonitor monitor,
Predicate<Loader> loaderFilter, LoadSpecChooser loadSpecChooser,
String importNameOverride, OptionChooser optionChooser) throws IOException,
CancelledException, DuplicateNameException, InvalidNameException, VersionException,
LoadException {
if (provider == null) { if (provider == null) {
return null; throw new LoadException("Cannot load null provider");
} }
// Get the load spec // Get the load spec
LoadSpec loadSpec = getLoadSpec(loaderFilter, loadSpecChooser, provider); LoadSpec loadSpec = getLoadSpec(loaderFilter, loadSpecChooser, provider);
if (loadSpec == null) { if (loadSpec == null) {
return null; throw new LoadException("No load spec found");
} }
// Get the program name // Get the preferred import name
String programName = loadSpec.getLoader().getPreferredFileName(provider); String importName = loadSpec.getLoader().getPreferredFileName(provider);
if (programNameOverride != null) { if (importNameOverride != null) {
programName = programNameOverride; importName = importNameOverride;
} }
// Collect options // Collect options
@ -185,21 +483,40 @@ public final class AutoImporter {
AddressFactory addrFactory = null;// Address type options not permitted if null AddressFactory addrFactory = null;// Address type options not permitted if null
if (languageCompilerSpecPair != null) { if (languageCompilerSpecPair != null) {
// It is assumed that if languageCompilerSpecPair exists, then language will be found // It is assumed that if languageCompilerSpecPair exists, then language will be found
addrFactory = DefaultLanguageService.getLanguageService().getLanguage( addrFactory = DefaultLanguageService.getLanguageService()
languageCompilerSpecPair.languageID).getAddressFactory(); .getLanguage(
languageCompilerSpecPair.languageID)
.getAddressFactory();
} }
List<Option> loaderOptions = optionChooser.choose( List<Option> loaderOptions = optionChooser.choose(
loadSpec.getLoader().getDefaultOptions(provider, loadSpec, null, false), addrFactory); loadSpec.getLoader().getDefaultOptions(provider, loadSpec, null, false), addrFactory);
if (loaderOptions == null) { if (loaderOptions == null) {
return null; throw new LoadException("Cannot load with null options");
} }
// Import program // Import
Msg.info(AutoImporter.class, "Using Loader: " + loadSpec.getLoader().getName()); Msg.info(AutoImporter.class, "Using Loader: " + loadSpec.getLoader().getName());
List<DomainObject> domainObjects = loadSpec.getLoader().load(provider, programName, Msg.info(AutoImporter.class,
programFolder, loadSpec, loaderOptions, messageLog, consumer, monitor); "Using Language/Compiler: " + loadSpec.getLanguageCompilerSpec());
LoadResults<? extends DomainObject> loadResults = loadSpec.getLoader()
.load(provider, importName, project, projectFolderPath, loadSpec, loaderOptions,
messageLog, consumer, monitor);
return multipleProgramsStrategy.handlePrograms(getPrograms(domainObjects), consumer); // Filter out and release non-Programs
List<Loaded<Program>> loadedPrograms = new ArrayList<>();
for (Loaded<? extends DomainObject> loaded : loadResults) {
if (loaded.getDomainObject() instanceof Program program) {
loadedPrograms.add(
new Loaded<Program>(program, loaded.getName(), loaded.getProjectFolderPath()));
}
else {
loaded.release(consumer);
}
}
if (loadedPrograms.isEmpty()) {
throw new LoadException("Domain objects were loaded, but none were Programs");
}
return new LoadResults<>(loadedPrograms);
} }
private static LoadSpec getLoadSpec(Predicate<Loader> loaderFilter, private static LoadSpec getLoadSpec(Predicate<Loader> loaderFilter,
@ -216,15 +533,4 @@ public final class AutoImporter {
Msg.info(AutoImporter.class, "No load spec found for import file: " + name); Msg.info(AutoImporter.class, "No load spec found for import file: " + name);
return null; return null;
} }
private static List<Program> getPrograms(List<DomainObject> domainObjects) {
List<Program> programs = new ArrayList<Program>();
for (DomainObject domainObject : domainObjects) {
if (domainObject instanceof Program) {
programs.add((Program) domainObject);
}
}
return programs;
}
} }

View file

@ -1,58 +0,0 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.util.importer;
import ghidra.program.model.listing.Program;
import java.util.List;
public interface MultipleProgramsStrategy {
public static final MultipleProgramsStrategy ALL_PROGRAMS = new MultipleProgramsStrategy() {
public List<Program> handlePrograms(List<Program> programs,
Object consumer) {
return programs;
}
};
public static final MultipleProgramsStrategy ONE_PROGRAM_OR_EXCEPTION = new MultipleProgramsStrategy() {
public List<Program> handlePrograms(List<Program> programs,
Object consumer) {
if (programs != null && programs.size() > 1) {
for (Program program : programs) {
program.release(consumer);
}
throw new MultipleProgramsException();
}
return programs;
}
};
public static final MultipleProgramsStrategy ONE_PROGRAM_OR_NULL = new MultipleProgramsStrategy() {
public List<Program> handlePrograms(List<Program> programs,
Object consumer) {
if (programs != null && programs.size() > 1) {
for (Program program : programs) {
program.release(consumer);
}
return null;
}
return programs;
}
};
List<Program> handlePrograms(List<Program> programs, Object consumer);
}

View file

@ -24,6 +24,7 @@ import java.util.stream.Collectors;
import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.FilenameUtils;
import ghidra.app.util.Option; import ghidra.app.util.Option;
import ghidra.app.util.OptionUtils;
import ghidra.app.util.bin.ByteProvider; import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.bin.FileByteProvider; import ghidra.app.util.bin.FileByteProvider;
import ghidra.app.util.importer.LibrarySearchPathManager; import ghidra.app.util.importer.LibrarySearchPathManager;
@ -85,23 +86,24 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
throws CancelledException, IOException; throws CancelledException, IOException;
@Override @Override
protected List<LoadedProgram> loadProgram(ByteProvider provider, String programName, protected List<Loaded<Program>> loadProgram(ByteProvider provider, String loadedName,
DomainFolder programFolder, LoadSpec loadSpec, List<Option> options, MessageLog log, Project project, String projectFolderPath, LoadSpec loadSpec, List<Option> options,
Object consumer, TaskMonitor monitor) throws CancelledException, IOException { MessageLog log, Object consumer, TaskMonitor monitor)
throws CancelledException, IOException {
List<LoadedProgram> loadedProgramList = new ArrayList<>(); List<Loaded<Program>> loadedProgramList = new ArrayList<>();
List<String> libraryNameList = new ArrayList<>(); List<String> libraryNameList = new ArrayList<>();
boolean success = false; boolean success = false;
try { try {
// Load the primary program // Load the primary program
Program program = doLoad(provider, programName, programFolder, loadSpec, Program program = doLoad(provider, loadedName, loadSpec, libraryNameList, options,
libraryNameList, options, consumer, log, monitor); consumer, log, monitor);
loadedProgramList.add(new LoadedProgram(program, programFolder)); loadedProgramList.add(new Loaded<>(program, loadedName, projectFolderPath));
// Load the libraries // Load the libraries
List<LoadedProgram> libraries = loadLibraries(provider, program, programFolder, List<Loaded<Program>> libraries = loadLibraries(provider, program, project,
loadSpec, options, log, consumer, libraryNameList, monitor); projectFolderPath, loadSpec, options, log, consumer, libraryNameList, monitor);
loadedProgramList.addAll(libraries); loadedProgramList.addAll(libraries);
success = true; success = true;
@ -115,35 +117,46 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
} }
@Override @Override
protected boolean loadProgramInto(ByteProvider provider, LoadSpec loadSpec, protected void loadProgramInto(ByteProvider provider, LoadSpec loadSpec,
List<Option> options, MessageLog log, Program program, TaskMonitor monitor) List<Option> options, MessageLog log, Program program, TaskMonitor monitor)
throws CancelledException, IOException { throws CancelledException, LoadException, IOException {
LanguageCompilerSpecPair pair = loadSpec.getLanguageCompilerSpec(); LanguageCompilerSpecPair pair = loadSpec.getLanguageCompilerSpec();
LanguageID languageID = program.getLanguageID(); LanguageID languageID = program.getLanguageID();
CompilerSpecID compilerSpecID = program.getCompilerSpec().getCompilerSpecID(); CompilerSpecID compilerSpecID = program.getCompilerSpec().getCompilerSpecID();
if (!(pair.languageID.equals(languageID) && pair.compilerSpecID.equals(compilerSpecID))) { if (!(pair.languageID.equals(languageID) && pair.compilerSpecID.equals(compilerSpecID))) {
log.appendMsg(provider.getAbsolutePath() + String message = provider.getAbsolutePath() +
" does not have the same language/compiler spec as program " + program.getName()); " does not have the same language/compiler spec as program " + program.getName();
return false; log.appendMsg(message);
throw new LoadException(message);
} }
log.appendMsg("----- Loading " + provider.getAbsolutePath() + " -----"); log.appendMsg("----- Loading " + provider.getAbsolutePath() + " -----");
load(provider, loadSpec, options, program, monitor, log); load(provider, loadSpec, options, program, monitor, log);
return true;
} }
@Override @Override
protected void postLoadProgramFixups(List<LoadedProgram> loadedPrograms, List<Option> options, protected void postLoadProgramFixups(List<Loaded<Program>> loadedPrograms, Project project,
MessageLog messageLog, TaskMonitor monitor) throws CancelledException, IOException { List<Option> options, MessageLog messageLog, TaskMonitor monitor)
throws CancelledException, IOException {
if (loadedPrograms.isEmpty()) { if (loadedPrograms.isEmpty()) {
return; return;
} }
if (isLinkExistingLibraries(options) || isLoadLocalLibraries(options) || if (isLinkExistingLibraries(options) || isLoadLocalLibraries(options) ||
isLoadSystemLibraries(options)) { isLoadSystemLibraries(options)) {
DomainFolder programFolder = loadedPrograms.get(0).destinationFolder(); String projectFolderPath = loadedPrograms.get(0).getProjectFolderPath();
DomainFolder linkSearchFolder = getLinkSearchFolder(programFolder, options); List<DomainFolder> searchFolders = new ArrayList<>();
fixupExternalLibraries(loadedPrograms.stream().map(e -> e.program()).toList(), String destPath = getLibraryDestinationFolderPath(project, projectFolderPath, options);
linkSearchFolder, true, messageLog, monitor); DomainFolder destSearchFolder =
getLibraryDestinationSearchFolder(project, destPath, options);
DomainFolder linkSearchFolder =
getLinkSearchFolder(project, projectFolderPath, options);
if (destSearchFolder != null) {
searchFolders.add(destSearchFolder);
}
if (linkSearchFolder != null) {
searchFolders.add(linkSearchFolder);
}
fixupExternalLibraries(loadedPrograms, searchFolders, messageLog, monitor);
} }
} }
@ -200,6 +213,11 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
if (!String.class.isAssignableFrom(option.getValueClass())) { if (!String.class.isAssignableFrom(option.getValueClass())) {
return "Invalid type for option: " + name + " - " + option.getValueClass(); return "Invalid type for option: " + name + " - " + option.getValueClass();
} }
String value = (String) option.getValue();
if (!value.isEmpty() && !value.startsWith("/")) {
return "Invalid absolute project path for option: " + name;
}
} }
} }
} }
@ -213,46 +231,41 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
* @return True if existing libraries should be linked; otherwise, false * @return True if existing libraries should be linked; otherwise, false
*/ */
protected boolean isLinkExistingLibraries(List<Option> options) { protected boolean isLinkExistingLibraries(List<Option> options) {
boolean isLinkExistingLibraries = LINK_EXISTING_OPTION_DEFAULT; return OptionUtils.getOption(LINK_EXISTING_OPTION_NAME, options,
if (options != null) { LINK_EXISTING_OPTION_DEFAULT);
for (Option option : options) {
String optName = option.getName();
if (optName.equals(LINK_EXISTING_OPTION_NAME)) {
isLinkExistingLibraries = (Boolean) option.getValue();
}
}
}
return isLinkExistingLibraries;
} }
/** /**
* Gets the {@link DomainFolder project folder} to search for existing libraries * Gets the {@link DomainFolder project folder} to search for existing libraries
* *
* @param programFolder The {@link DomainFolder} that the main program is being loaded into * @param project The {@link Project}. Could be null if there is no project.
* @param projectFolderPath The project folder path the program will get saved to. Could be null
* if the program is not getting saved to the project.
* @param options a {@link List} of {@link Option}s * @param options a {@link List} of {@link Option}s
* @return The path of the project folder to search for existing libraries, or null if no * @return The path of the project folder to search for existing libraries, or null if no
* project folders should be searched * project folders can be or should be searched
*/ */
protected DomainFolder getLinkSearchFolder(DomainFolder programFolder, protected DomainFolder getLinkSearchFolder(Project project, String projectFolderPath,
List<Option> options) { List<Option> options) {
if (!shouldSearchAllPaths(options) && !isLinkExistingLibraries(options)) { if (!shouldSearchAllPaths(options) && !isLinkExistingLibraries(options)) {
return null; return null;
} }
String folderPath = LINK_SEARCH_FOLDER_OPTION_DEFAULT; if (project == null) {
if (options != null) { return null;
for (Option option : options) {
String optName = option.getName();
if (optName.equals(LINK_SEARCH_FOLDER_OPTION_NAME)) {
folderPath = (String) option.getValue();
}
}
} }
if (folderPath.equals(LINK_SEARCH_FOLDER_OPTION_DEFAULT)) { String linkSearchFolderPath = OptionUtils.getOption(LINK_SEARCH_FOLDER_OPTION_NAME, options,
return programFolder; LINK_SEARCH_FOLDER_OPTION_DEFAULT);
ProjectData projectData = project.getProjectData();
if (linkSearchFolderPath.isBlank()) {
if (projectFolderPath == null) {
return null;
}
return projectData.getFolder(projectFolderPath);
} }
return programFolder.getProjectData().getFolder(FilenameUtils.separatorsToUnix(folderPath)); return projectData.getFolder(FilenameUtils.separatorsToUnix(linkSearchFolderPath));
} }
/** /**
@ -263,16 +276,8 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
* @return True if local libraries should be loaded; otherwise, false * @return True if local libraries should be loaded; otherwise, false
*/ */
protected boolean isLoadLocalLibraries(List<Option> options) { protected boolean isLoadLocalLibraries(List<Option> options) {
boolean isLoadLocalLibraries = LOCAL_LIBRARY_OPTION_DEFAULT; return OptionUtils.getOption(LOCAL_LIBRARY_OPTION_NAME, options,
if (options != null) { LOCAL_LIBRARY_OPTION_DEFAULT);
for (Option option : options) {
String optName = option.getName();
if (optName.equals(LOCAL_LIBRARY_OPTION_NAME)) {
isLoadLocalLibraries = (Boolean) option.getValue();
}
}
}
return isLoadLocalLibraries;
} }
/** /**
@ -283,16 +288,8 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
* @return True if system libraries should be loaded; otherwise, false * @return True if system libraries should be loaded; otherwise, false
*/ */
protected boolean isLoadSystemLibraries(List<Option> options) { protected boolean isLoadSystemLibraries(List<Option> options) {
boolean isLoadSystemLibraries = SYSTEM_LIBRARY_OPTION_DEFAULT; return OptionUtils.getOption(SYSTEM_LIBRARY_OPTION_NAME, options,
if (options != null) { SYSTEM_LIBRARY_OPTION_DEFAULT);
for (Option option : options) {
String optName = option.getName();
if (optName.equals(SYSTEM_LIBRARY_OPTION_NAME)) {
isLoadSystemLibraries = (Boolean) option.getValue();
}
}
}
return isLoadSystemLibraries;
} }
/** /**
@ -302,42 +299,57 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
* @return The desired recursive library load depth * @return The desired recursive library load depth
*/ */
protected int getLibraryLoadDepth(List<Option> options) { protected int getLibraryLoadDepth(List<Option> options) {
int depth = DEPTH_OPTION_DEFAULT; return OptionUtils.getOption(DEPTH_OPTION_NAME, options, DEPTH_OPTION_DEFAULT);
if (options != null) {
for (Option option : options) {
String optName = option.getName();
if (optName.equals(DEPTH_OPTION_NAME)) {
depth = (int) option.getValue();
}
}
}
return depth;
} }
/** /**
* Gets the {@link DomainFolder project folder} to load the libraries into * Gets the project folder path to load the libraries into. It does not have to exist in the
* project yet.
* *
* @param programFolder The {@link DomainFolder} that the main program is being loaded into * @param project The {@link Project}. Could be null if there is no project.
* @param projectFolderPath The project folder path the program will get saved to. Could be null
* if the program is not getting saved to the project.
* @param options a {@link List} of {@link Option}s * @param options a {@link List} of {@link Option}s
* @return The path of the project folder to load the libraries into * @return The path of the project folder to load the libraries into. Could be null if the
* specified project is null or a destination folder path could not be determined.
*/ */
protected DomainFolder getLibraryDestinationFolder(DomainFolder programFolder, protected String getLibraryDestinationFolderPath(Project project, String projectFolderPath,
List<Option> options) { List<Option> options) {
String folderPath = LIBRARY_DEST_FOLDER_OPTION_DEFAULT; if (project == null) {
if (options != null) { return null;
for (Option option : options) {
String optName = option.getName();
if (optName.equals(LIBRARY_DEST_FOLDER_OPTION_NAME)) {
folderPath = (String) option.getValue();
}
}
} }
if (folderPath.equals(LIBRARY_DEST_FOLDER_OPTION_DEFAULT)) { String libraryDestinationFolderPath = OptionUtils.getOption(LIBRARY_DEST_FOLDER_OPTION_NAME,
return programFolder; options, LIBRARY_DEST_FOLDER_OPTION_DEFAULT);
if (libraryDestinationFolderPath.isBlank()) {
return projectFolderPath;
} }
return programFolder.getProjectData().getFolder(FilenameUtils.separatorsToUnix(folderPath)); return FilenameUtils.separatorsToUnix(libraryDestinationFolderPath);
}
/**
* Gets the {@link DomainFolder project folder} that libraries are loaded into, to search for
* existing libraries. It will only be returned if the options to load new libraries into the
* project are set.
*
* @param project The {@link Project}. Could be null if there is no project.
* @param libraryDestinationFolderPath The path of the project folder to load the libraries
* into. Could be null (@see #getLibraryDestinationFolderPath(Project, String, List)).
* @param options a {@link List} of {@link Option}s
* @return The path of the destination project folder to search for existing libraries, or null
* if the destination folder is not being used or should not be searched
*/
protected DomainFolder getLibraryDestinationSearchFolder(Project project,
String libraryDestinationFolderPath, List<Option> options) {
if (project == null || libraryDestinationFolderPath == null) {
return null;
}
if (!isLoadLocalLibraries(options) && !isLoadSystemLibraries(options)) {
return null;
}
return project.getProjectData().getFolder(libraryDestinationFolderPath);
} }
/** /**
@ -424,8 +436,8 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
* @param loadSpec The {@link LoadSpec} used for the load * @param loadSpec The {@link LoadSpec} used for the load
* @param options The options * @param options The options
* @param log The log * @param log The log
* @param monitor A cancel.able monitor * @param monitor A cancelable monitor
* @return True if the library should be saved to the project; otherwise, false * @return True if the library should be loaded into the project; otherwise, false
* @throws IOException If an IO-related error occurred * @throws IOException If an IO-related error occurred
* @throws CancelledException If the user cancelled the action * @throws CancelledException If the user cancelled the action
*/ */
@ -440,9 +452,9 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
* *
* @param provider The {@link ByteProvider} of the program being loaded * @param provider The {@link ByteProvider} of the program being loaded
* @param program The {@link Program} being loaded * @param program The {@link Program} being loaded
* @param programFolder The domain folder where the new program will be stored, if null * @param project The {@link Project}. Could be null if there is no project.
* the program should not be pre-saved. NOTE: the newly imported libraries will not be written * @param projectFolderPath The project folder path the program will get saved to. Could be null
* to this folder yet, that is handled in a later follow on step. * if the program is not getting saved to the project.
* @param desiredLoadSpec The desired {@link LoadSpec} * @param desiredLoadSpec The desired {@link LoadSpec}
* @param options The load options * @param options The load options
* @param log The log * @param log The log
@ -453,19 +465,25 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
* @throws IOException if there was an IO-related problem loading * @throws IOException if there was an IO-related problem loading
* @throws CancelledException if the user cancelled the load * @throws CancelledException if the user cancelled the load
*/ */
private List<LoadedProgram> loadLibraries(ByteProvider provider, Program program, private List<Loaded<Program>> loadLibraries(ByteProvider provider, Program program,
DomainFolder programFolder, LoadSpec desiredLoadSpec, List<Option> options, Project project, String projectFolderPath, LoadSpec desiredLoadSpec,
MessageLog log, Object consumer, List<String> libraryNameList, TaskMonitor monitor) List<Option> options, MessageLog log, Object consumer, List<String> libraryNameList,
throws CancelledException, IOException { TaskMonitor monitor) throws CancelledException, IOException {
List<LoadedProgram> loadedPrograms = new ArrayList<>(); List<Loaded<Program>> loadedPrograms = new ArrayList<>();
Set<String> processed = new HashSet<>(); Program library = null;
Set<String> processed = new TreeSet<>(getLibraryNameComparator());
Queue<UnprocessedLibrary> unprocessed = Queue<UnprocessedLibrary> unprocessed =
createUnprocessedQueue(libraryNameList, getLibraryLoadDepth(options)); createUnprocessedQueue(libraryNameList, getLibraryLoadDepth(options));
List<String> searchPaths = getLibrarySearchPaths(provider, options); List<String> searchPaths = getLibrarySearchPaths(provider, options);
DomainFolder linkSearchFolder = getLinkSearchFolder(programFolder, options); DomainFolder linkSearchFolder = getLinkSearchFolder(project, projectFolderPath, options);
DomainFolder libraryDestFolder = getLibraryDestinationFolder(programFolder, options); String libraryDestFolderPath =
getLibraryDestinationFolderPath(project, projectFolderPath, options);
DomainFolder libraryDestFolder =
getLibraryDestinationSearchFolder(project, libraryDestFolderPath, options);
boolean success = false;
try {
while (!unprocessed.isEmpty()) { while (!unprocessed.isEmpty()) {
monitor.checkCanceled(); monitor.checkCanceled();
UnprocessedLibrary unprocessedLibrary = unprocessed.remove(); UnprocessedLibrary unprocessedLibrary = unprocessed.remove();
@ -476,7 +494,12 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
} }
processed.add(libraryName); processed.add(libraryName);
boolean foundLibrary = false; boolean foundLibrary = false;
if (linkSearchFolder != null && findLibrary(libraryName, linkSearchFolder) != null) { if (libraryDestFolder != null &&
findLibrary(libraryName, libraryDestFolder) != null) {
log.appendMsg("Library " + libraryName + ": Already loaded ");
}
else if (linkSearchFolder != null &&
findLibrary(libraryName, linkSearchFolder) != null) {
log.appendMsg("Library " + libraryName + ": Already loaded "); log.appendMsg("Library " + libraryName + ": Already loaded ");
} }
else if (!searchPaths.isEmpty()) { else if (!searchPaths.isEmpty()) {
@ -487,35 +510,45 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
for (File candidateLibraryFile : candidateLibraryFiles) { for (File candidateLibraryFile : candidateLibraryFiles) {
monitor.checkCanceled(); monitor.checkCanceled();
List<String> newLibraryList = new ArrayList<>(); List<String> newLibraryList = new ArrayList<>();
Program library = library = loadLibrary(simpleLibraryName, candidateLibraryFile,
loadLibrary(simpleLibraryName, programFolder, candidateLibraryFile,
desiredLoadSpec, newLibraryList, options, consumer, log, monitor); desiredLoadSpec, newLibraryList, options, consumer, log, monitor);
for (String newLibraryName : newLibraryList) { for (String newLibraryName : newLibraryList) {
unprocessed.add(new UnprocessedLibrary(newLibraryName, depth - 1)); unprocessed.add(new UnprocessedLibrary(newLibraryName, depth - 1));
} }
if (library != null) { if (library == null) {
continue;
}
foundLibrary = true; foundLibrary = true;
log.appendMsg(
"Library " + libraryName + ": Examining " + candidateLibraryFile);
if (processLibrary(library, libraryName, candidateLibraryFile, provider, if (processLibrary(library, libraryName, candidateLibraryFile, provider,
desiredLoadSpec, options, log, monitor)) { desiredLoadSpec, options, log, monitor)) {
loadedPrograms.add(new LoadedProgram(library, libraryDestFolder)); loadedPrograms.add(
log.appendMsg( new Loaded<Program>(library, libraryName, libraryDestFolderPath));
"Library " + libraryName + ": Saving " + candidateLibraryFile);
} }
else { else {
library.release(consumer); library.release(consumer);
log.appendMsg(
"Library " + libraryName + ": Examining " + candidateLibraryFile);
} }
library = null;
break; break;
} }
}
if (!foundLibrary) { if (!foundLibrary) {
log.appendMsg("Library " + libraryName + ": Not found"); log.appendMsg("Library " + libraryName + ": Not found");
} }
} }
} }
success = true;
return loadedPrograms; return loadedPrograms;
} }
finally {
if (!success) {
release(loadedPrograms, consumer);
if (library != null) {
library.release(consumer);
}
}
}
}
/** /**
* Find the library within the specified {@link DomainFolder folder}. This method will handle * Find the library within the specified {@link DomainFolder folder}. This method will handle
@ -542,7 +575,7 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
// Lookup by full project path // Lookup by full project path
// NOTE: probably no need to support optional extensions and case-insensitivity for this case // NOTE: probably no need to support optional extensions and case-insensitivity for this case
String projectPath = appendPath(folder.getPathname(), libraryPath); String projectPath = concatenatePaths(folder.getPathname(), libraryPath);
DomainFile ret = DomainFile ret =
folder.getProjectData().getFile(FilenameUtils.separatorsToUnix(projectPath)); folder.getProjectData().getFile(FilenameUtils.separatorsToUnix(projectPath));
if (ret != null) { if (ret != null) {
@ -611,7 +644,7 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
// 1) Try as possible subpath under the search path // 1) Try as possible subpath under the search path
String candidatePath = String candidatePath =
FilenameUtils.separatorsToSystem(appendPath(searchPath, libraryPath)); FilenameUtils.separatorsToSystem(concatenatePaths(searchPath, libraryPath));
File f = resolveLibraryFile(new File(candidatePath)); File f = resolveLibraryFile(new File(candidatePath));
if (f == null || !f.isFile()) { if (f == null || !f.isFile()) {
// 2) Fall back to looking for the library in the user specified search path, sans // 2) Fall back to looking for the library in the user specified search path, sans
@ -640,9 +673,6 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
* a {@link ByteProvider} available. * a {@link ByteProvider} available.
* *
* @param libraryName The name of the library to load * @param libraryName The name of the library to load
* @param libraryFolder The domain folder where the new library program will be stored, if null
* the program should not be pre-saved. NOTE: the newly imported libraries will not be written
* to this folder yet, that is handled in a later follow on step.
* @param libraryFile The library file to load * @param libraryFile The library file to load
* @param desiredLoadSpec The desired {@link LoadSpec} * @param desiredLoadSpec The desired {@link LoadSpec}
* @param libraryNameList A {@link List} to be populated with the given library's dependent * @param libraryNameList A {@link List} to be populated with the given library's dependent
@ -655,10 +685,9 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
* @throws CancelledException if the user cancelled the load operation * @throws CancelledException if the user cancelled the load operation
* @throws IOException if there was an IO-related error during the load * @throws IOException if there was an IO-related error during the load
*/ */
private Program loadLibrary(String libraryName, DomainFolder libraryFolder, File libraryFile, private Program loadLibrary(String libraryName, File libraryFile, LoadSpec desiredLoadSpec,
LoadSpec desiredLoadSpec, List<String> libraryNameList, List<Option> options, List<String> libraryNameList, List<Option> options, Object consumer, MessageLog log,
Object consumer, MessageLog log, TaskMonitor monitor) TaskMonitor monitor) throws CancelledException, IOException {
throws CancelledException, IOException {
try (ByteProvider provider = createLibraryByteProvider(libraryFile, desiredLoadSpec, log)) { try (ByteProvider provider = createLibraryByteProvider(libraryFile, desiredLoadSpec, log)) {
if (!shouldLoadLibrary(libraryName, libraryFile, provider, desiredLoadSpec, log)) { if (!shouldLoadLibrary(libraryName, libraryFile, provider, desiredLoadSpec, log)) {
@ -671,8 +700,8 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
return null; return null;
} }
Program library = doLoad(provider, libraryName, libraryFolder, libLoadSpec, Program library = doLoad(provider, libraryName, libLoadSpec, libraryNameList, options,
libraryNameList, options, consumer, log, monitor); consumer, log, monitor);
if (library == null) { if (library == null) {
log.appendMsg("Library " + libraryFile + " failed to load for some reason"); log.appendMsg("Library " + libraryFile + " failed to load for some reason");
@ -688,7 +717,6 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
* *
* @param provider The {@link ByteProvider} to load * @param provider The {@link ByteProvider} to load
* @param programName The name of the new program * @param programName The name of the new program
* @param programFolder The folder to load the program into
* @param loadSpec The {@link LoadSpec} * @param loadSpec The {@link LoadSpec}
* @param libraryNameList A {@link List} to be populated with the loaded program's dependent * @param libraryNameList A {@link List} to be populated with the loaded program's dependent
* library names * library names
@ -700,9 +728,9 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
* @throws CancelledException if the user cancelled the load operation * @throws CancelledException if the user cancelled the load operation
* @throws IOException if there was an IO-related error during the load * @throws IOException if there was an IO-related error during the load
*/ */
private Program doLoad(ByteProvider provider, String programName, DomainFolder programFolder, private Program doLoad(ByteProvider provider, String programName, LoadSpec loadSpec,
LoadSpec loadSpec, List<String> libraryNameList, List<Option> options, Object consumer, List<String> libraryNameList, List<Option> options, Object consumer, MessageLog log,
MessageLog log, TaskMonitor monitor) throws CancelledException, IOException { TaskMonitor monitor) throws CancelledException, IOException {
LanguageCompilerSpecPair pair = loadSpec.getLanguageCompilerSpec(); LanguageCompilerSpecPair pair = loadSpec.getLanguageCompilerSpec();
Language language = getLanguageService().getLanguage(pair.languageID); Language language = getLanguageService().getLanguage(pair.languageID);
CompilerSpec compilerSpec = language.getCompilerSpecByID(pair.compilerSpecID); CompilerSpec compilerSpec = language.getCompilerSpecByID(pair.compilerSpecID);
@ -743,7 +771,7 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
return program; return program;
} }
finally { finally {
program.endTransaction(transactionID, success); program.endTransaction(transactionID, true); // More efficient to commit when program will be discarded
if (!success) { if (!success) {
program.release(consumer); program.release(consumer);
program = null; program = null;
@ -752,39 +780,35 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
} }
/** /**
* For each program in the given list, fix up its external library entries so that they point * For each {@link Loaded} {@link Program} in the given list, fix up its external library
* to a path in the project. * entries so that they point to a path in the project.
* <p> * <p>
* Other programs in the given list are matched first, then the ghidraLibSearchFolders are * Other {@link Program}s in the given list are matched first, then the given
* searched for matches. * {@link DomainFolder search folder} is searched for matches.
* *
* @param programs the list of programs to resolve against each other. Programs not saved * @param loadedPrograms the list of {@link Loaded} {@link Program}s
* to the project will be considered as a valid external library. * @param searchFolders an ordered list of {@link DomainFolder}s which imported libraries will
* @param searchFolder the {@link DomainFolder} which imported libraries will be searched. * be searched. These folders will be searched if a library is not found within the list of
* This folder will be searched if a library is not found within the list of * programs supplied.
* programs supplied. If null, only the list of programs will be considered.
* @param saveIfModified flag to have this method save any programs it modifies
* @param messageLog log for messages. * @param messageLog log for messages.
* @param monitor the task monitor * @param monitor the task monitor
* @throws IOException if there was an IO-related problem resolving. * @throws IOException if there was an IO-related problem resolving.
* @throws CancelledException if the user cancelled the load. * @throws CancelledException if the user cancelled the load.
*/ */
private void fixupExternalLibraries(List<Program> programs, DomainFolder searchFolder, private void fixupExternalLibraries(List<Loaded<Program>> loadedPrograms,
boolean saveIfModified, MessageLog messageLog, TaskMonitor monitor) List<DomainFolder> searchFolders, MessageLog messageLog, TaskMonitor monitor)
throws CancelledException, IOException { throws CancelledException, IOException {
Map<String, Program> progsByName = programs.stream() Map<String, Loaded<Program>> loadedByName = loadedPrograms.stream()
.filter(Objects::nonNull)
.collect( .collect(
Collectors.toMap((p) -> p.getDomainFile().getName(), (p) -> p)); Collectors.toMap(loaded -> loaded.getName(), loaded -> loaded));
monitor.initialize(progsByName.size()); monitor.initialize(loadedByName.size());
for (Program program : progsByName.values()) { for (Loaded<Program> loadedProgram : loadedByName.values()) {
monitor.incrementProgress(1); monitor.incrementProgress(1);
if (monitor.isCancelled()) { monitor.checkCanceled();
return;
}
Program program = loadedProgram.getDomainObject();
ExternalManager extManager = program.getExternalManager(); ExternalManager extManager = program.getExternalManager();
String[] extLibNames = extManager.getExternalLibraryNames(); String[] extLibNames = extManager.getExternalLibraryNames();
if (extLibNames.length == 0 || if (extLibNames.length == 0 ||
@ -795,13 +819,10 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
monitor.setMessage("Resolving..." + program.getName()); monitor.setMessage("Resolving..." + program.getName());
int id = program.startTransaction("Resolving external references"); int id = program.startTransaction("Resolving external references");
try { try {
resolveExternalLibraries(program, progsByName, searchFolder, monitor, messageLog); resolveExternalLibraries(program, loadedByName, searchFolders, monitor, messageLog);
} }
finally { finally {
program.endTransaction(id, true); program.endTransaction(id, true);
if (saveIfModified && program.canSave() && program.isChanged()) {
program.save("Resolve external references", monitor);
}
} }
} }
} }
@ -812,21 +833,21 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
* Other programs in the map are matched first, then the ghidraLibSearchFolders * Other programs in the map are matched first, then the ghidraLibSearchFolders
* are searched for matches. * are searched for matches.
* *
* @param program the program whose Library entries are to be resolved. An open transaction * @param program the program whose Library entries are to be resolved. An open
* on program is required. * transaction on program is required.
* @param progsByName map of recently imported programs to be considered * @param loadedByName map of recently loaded things to be considered
* first when resolving external Libraries. Programs not saved to the project * first when resolving external Libraries. Programs not saved to the project
* will be ignored. * will be ignored.
* @param searchFolder the {@link DomainFolder} which imported libraries will be searched. * @param searchFolders an order list of {@link DomainFolder}s which imported libraries will be
* This folder will be searched if a library is not found within the list of * searched. These folders will be searched if a library is not found within the list of
* programs supplied. If null, only the list of programs will be considered. * programs supplied.
* @param messageLog log for messages. * @param messageLog log for messages.
* @param monitor the task monitor * @param monitor the task monitor
* @throws CancelledException if the user cancelled the load. * @throws CancelledException if the user cancelled the load.
*/ */
private void resolveExternalLibraries(Program program, Map<String, Program> progsByName, private void resolveExternalLibraries(Program program,
DomainFolder searchFolder, TaskMonitor monitor, MessageLog messageLog) Map<String, Loaded<Program>> loadedByName, List<DomainFolder> searchFolders,
throws CancelledException { TaskMonitor monitor, MessageLog messageLog) throws CancelledException {
ExternalManager extManager = program.getExternalManager(); ExternalManager extManager = program.getExternalManager();
String[] extLibNames = extManager.getExternalLibraryNames(); String[] extLibNames = extManager.getExternalLibraryNames();
messageLog.appendMsg("Linking external programs to " + program.getName() + "..."); messageLog.appendMsg("Linking external programs to " + program.getName() + "...");
@ -837,22 +858,27 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
monitor.checkCanceled(); monitor.checkCanceled();
try { try {
String externalFileName = FilenameUtils.getName(externalLibName); String externalFileName = FilenameUtils.getName(externalLibName);
DomainObject matchingExtProgram = findLibrary(progsByName, externalFileName); Loaded<Program> matchingExtProgram = findLibrary(loadedByName, externalFileName);
if (matchingExtProgram != null && matchingExtProgram.getDomainFile().exists()) { if (matchingExtProgram != null) {
extManager.setExternalPath(externalLibName, String path =
matchingExtProgram.getDomainFile().getPathname(), false); matchingExtProgram.getProjectFolderPath() + matchingExtProgram.getName();
messageLog.appendMsg(" [" + externalLibName + "] -> [" + extManager.setExternalPath(externalLibName, path, false);
matchingExtProgram.getDomainFile().getPathname() + "]"); messageLog.appendMsg(" [" + externalLibName + "] -> [" + path + "]");
} }
else { else {
boolean found = false;
for (DomainFolder searchFolder : searchFolders) {
DomainFile alreadyImportedLib = findLibrary(externalLibName, searchFolder); DomainFile alreadyImportedLib = findLibrary(externalLibName, searchFolder);
if (alreadyImportedLib != null) { if (alreadyImportedLib != null) {
extManager.setExternalPath(externalLibName, extManager.setExternalPath(externalLibName,
alreadyImportedLib.getPathname(), false); alreadyImportedLib.getPathname(), false);
messageLog.appendMsg(" [" + externalLibName + "] -> [" + messageLog.appendMsg(" [" + externalLibName + "] -> [" +
alreadyImportedLib.getPathname() + "] (previously imported)"); alreadyImportedLib.getPathname() + "] (previously imported)");
found = true;
break;
} }
else { }
if (!found) {
messageLog.appendMsg(" [" + externalLibName + "] -> not found"); messageLog.appendMsg(" [" + externalLibName + "] -> not found");
} }
} }
@ -908,53 +934,26 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
/** /**
* Find the library within the given {@link Map} of {@link Program}s * Find the library within the given {@link Map} of {@link Program}s
* *
* @param programsByName The map to search * @param loadedByName The map to search
* @param libraryName The library name to lookup * @param libraryName The library name to lookup
* @return The found {@link Program} or null if not found * @return The found {@link Loaded} {@link Program} or null if not found
*/ */
private Program findLibrary(Map<String, Program> programsByName, String libraryName) { private Loaded<Program> findLibrary(Map<String, Loaded<Program>> loadedByName,
String libraryName) {
Comparator<String> comparator = getLibraryNameComparator(); Comparator<String> comparator = getLibraryNameComparator();
boolean noExtension = FilenameUtils.getExtension(libraryName).equals(""); boolean noExtension = FilenameUtils.getExtension(libraryName).equals("");
for (String key : programsByName.keySet()) { for (String key : loadedByName.keySet()) {
String candidateName = key; String candidateName = key;
if (isOptionalLibraryFilenameExtensions() && noExtension) { if (isOptionalLibraryFilenameExtensions() && noExtension) {
candidateName = FilenameUtils.getBaseName(candidateName); candidateName = FilenameUtils.getBaseName(candidateName);
} }
if (comparator.compare(candidateName, libraryName) == 0) { if (comparator.compare(candidateName, libraryName) == 0) {
return programsByName.get(key); return loadedByName.get(key);
} }
} }
return null; return null;
} }
/**
* Appends the given path elements to form a single path
*
* @param pathElements The path elements to append to one another
* @return A single path consisting of the given path elements appended together
*/
private String appendPath(String... pathElements) {
StringBuilder sb = new StringBuilder();
for (String pathElement : pathElements) {
if (pathElement == null || pathElement.isEmpty()) {
continue;
}
boolean sbEndsWithSlash =
sb.length() > 0 && "/\\".indexOf(sb.charAt(sb.length() - 1)) != -1;
boolean elementStartsWithSlash = "/\\".indexOf(pathElement.charAt(0)) != -1;
if (!sbEndsWithSlash && !elementStartsWithSlash && sb.length() > 0) {
sb.append("/");
}
else if (elementStartsWithSlash && sbEndsWithSlash) {
pathElement = pathElement.substring(1);
}
sb.append(pathElement);
}
return sb.toString();
}
/** /**
* Ensures the given {@link LoadSpec} matches one supported by the loader * Ensures the given {@link LoadSpec} matches one supported by the loader
* *

View file

@ -21,9 +21,11 @@ import java.util.Iterator;
import java.util.List; import java.util.List;
import ghidra.app.util.Option; import ghidra.app.util.Option;
import ghidra.app.util.OptionUtils;
import ghidra.app.util.bin.ByteProvider; import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.importer.MessageLog; import ghidra.app.util.importer.MessageLog;
import ghidra.framework.model.DomainObject; import ghidra.framework.model.DomainObject;
import ghidra.framework.model.Project;
import ghidra.framework.options.Options; import ghidra.framework.options.Options;
import ghidra.program.model.listing.Function; import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
@ -116,33 +118,33 @@ public abstract class AbstractOrdinalSupportLoader extends AbstractLibrarySuppor
} }
@Override @Override
protected void postLoadProgramFixups(List<LoadedProgram> loadedPrograms, List<Option> options, protected void postLoadProgramFixups(List<Loaded<Program>> loadedPrograms, Project project,
MessageLog messageLog, TaskMonitor monitor) throws CancelledException, IOException { List<Option> options, MessageLog messageLog, TaskMonitor monitor)
throws CancelledException, IOException {
monitor.initialize(loadedPrograms.size()); monitor.initialize(loadedPrograms.size());
if (shouldPerformOrdinalLookup(options)) { if (shouldPerformOrdinalLookup(options)) {
for (LoadedProgram loadedProgram : loadedPrograms) { for (Loaded<Program> loadedProgram : loadedPrograms) {
monitor.checkCanceled(); monitor.checkCanceled();
Program program = loadedProgram.getDomainObject();
Program p = loadedProgram.program(); int id = program.startTransaction("Ordinal fixups");
int id = p.startTransaction("Ordinal fixups");
boolean success = false;
try { try {
applyLibrarySymbols(p, messageLog, monitor); applyLibrarySymbols(program, messageLog, monitor);
applyImports(p, messageLog, monitor); applyImports(program, messageLog, monitor);
success = true;
} }
finally { finally {
p.endTransaction(id, success); program.endTransaction(id, true); // More efficient to commit when program will be discarded
if (p.canSave() && p.isChanged()) {
p.save("Ordinal fixups", monitor);
} }
} }
} }
}
LibraryLookupTable.cleanup();
super.postLoadProgramFixups(loadedPrograms, options, messageLog, monitor); super.postLoadProgramFixups(loadedPrograms, project, options, messageLog, monitor);
}
@Override
protected void postLoadCleanup(boolean success) {
super.postLoadCleanup(success);
LibraryLookupTable.cleanup();
} }
/** /**
@ -152,16 +154,8 @@ public abstract class AbstractOrdinalSupportLoader extends AbstractLibrarySuppor
* @return True if ordinal lookup should be performed; otherwise, false * @return True if ordinal lookup should be performed; otherwise, false
*/ */
private boolean shouldPerformOrdinalLookup(List<Option> options) { private boolean shouldPerformOrdinalLookup(List<Option> options) {
boolean performOrdinalLookup = ORDINAL_LOOKUP_OPTION_DEFAULT; return OptionUtils.getOption(ORDINAL_LOOKUP_OPTION_NAME, options,
if (options != null) { ORDINAL_LOOKUP_OPTION_DEFAULT);
for (Option option : options) {
String optName = option.getName();
if (optName.equals(ORDINAL_LOOKUP_OPTION_NAME)) {
performOrdinalLookup = (Boolean) option.getValue();
}
}
}
return performOrdinalLookup;
} }
/** /**

View file

@ -33,13 +33,15 @@ import ghidra.program.database.ProgramDB;
import ghidra.program.database.function.OverlappingFunctionException; import ghidra.program.database.function.OverlappingFunctionException;
import ghidra.program.model.address.*; import ghidra.program.model.address.*;
import ghidra.program.model.lang.*; import ghidra.program.model.lang.*;
import ghidra.program.model.listing.*; import ghidra.program.model.listing.FunctionManager;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.InvalidAddressException; import ghidra.program.model.mem.InvalidAddressException;
import ghidra.program.model.mem.MemoryConflictException; import ghidra.program.model.mem.MemoryConflictException;
import ghidra.program.model.symbol.*; import ghidra.program.model.symbol.*;
import ghidra.program.util.DefaultLanguageService; import ghidra.program.util.DefaultLanguageService;
import ghidra.program.util.GhidraProgramUtilities; import ghidra.program.util.GhidraProgramUtilities;
import ghidra.util.*; import ghidra.util.HashUtilities;
import ghidra.util.MD5Utilities;
import ghidra.util.exception.*; import ghidra.util.exception.*;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
@ -56,34 +58,45 @@ public abstract class AbstractProgramLoader implements Loader {
public static final String ANCHOR_LABELS_OPTION_NAME = "Anchor Processor Defined Labels"; public static final String ANCHOR_LABELS_OPTION_NAME = "Anchor Processor Defined Labels";
/** /**
* A {@link Program} with its associated {@link DomainFolder destination folder} * Loads bytes in a particular format as a new {@link Loaded} {@link Program}. Multiple
*
* @param program The {@link Program}
* @param destinationFolder The {@link DomainFolder} where the program will get loaded to
*/
public record LoadedProgram(Program program, DomainFolder destinationFolder) {/**/}
/**
* Loads program bytes in a particular format as a new {@link Program}. Multiple
* {@link Program}s may end up getting created, depending on the nature of the format. * {@link Program}s may end up getting created, depending on the nature of the format.
* <p>
* Note that when the load completes, the returned {@link Loaded} {@link Program}s are not
* saved to a project. That is the responsibility of the caller (see
* {@link Loaded#save(Project, MessageLog, TaskMonitor)}).
* <p>
* It is also the responsibility of the caller to release the returned {@link Loaded}
* {@link Program}s with {@link Loaded#release(Object)} when they are no longer
* needed.
* *
* @param provider The bytes to load. * @param provider The bytes to load.
* @param programName The name of the {@link Program} that's being loaded. * @param loadedName A suggested name for the primary {@link Loaded} {@link Program}.
* @param programFolder The {@link DomainFolder} where the loaded thing should be saved. Could * This is just a suggestion, and a {@link Loader} implementation reserves the right to change
* be null if the thing should not be pre-saved. * 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 loadSpec The {@link LoadSpec} to use during load.
* @param options The load options. * @param options The load options.
* @param log The message log. * @param log The message log.
* @param consumer A consumer object for {@link Program}s generated. * @param consumer A consumer object for generated {@link Program}s.
* @param monitor A cancelable task monitor. * @param monitor A task monitor.
* @return A list of {@link LoadedProgram loaded programs} (element 0 corresponds to primary * @return A {@link List} of one or more {@link Loaded} {@link Program}s (created but not
* loaded {@link Program}). * saved).
* @throws LoadException if the load failed in an expected way.
* @throws IOException if there was an IO-related problem loading. * @throws IOException if there was an IO-related problem loading.
* @throws CancelledException if the user cancelled the load. * @throws CancelledException if the user cancelled the load.
*/ */
protected abstract List<LoadedProgram> loadProgram(ByteProvider provider, String programName, protected abstract List<Loaded<Program>> loadProgram(ByteProvider provider, String loadedName,
DomainFolder programFolder, LoadSpec loadSpec, List<Option> options, MessageLog log, Project project, String projectFolderPath, LoadSpec loadSpec, List<Option> options,
Object consumer, TaskMonitor monitor) throws IOException, CancelledException; MessageLog log, Object consumer, TaskMonitor monitor)
throws IOException, LoadException, CancelledException;
/** /**
* Loads program bytes into the specified {@link Program}. This method will not create any new * Loads program bytes into the specified {@link Program}. This method will not create any new
@ -97,109 +110,66 @@ public abstract class AbstractProgramLoader implements Loader {
* @param messageLog The message log. * @param messageLog The message log.
* @param program The {@link Program} to load into. * @param program The {@link Program} to load into.
* @param monitor A cancelable task monitor. * @param monitor A cancelable task monitor.
* @return True if the file was successfully loaded; otherwise, false. * @throws LoadException if the load failed in an expected way.
* @throws IOException if there was an IO-related problem loading. * @throws IOException if there was an IO-related problem loading.
* @throws CancelledException if the user cancelled the load. * @throws CancelledException if the user cancelled the load.
*/ */
protected abstract boolean loadProgramInto(ByteProvider provider, LoadSpec loadSpec, protected abstract void loadProgramInto(ByteProvider provider, LoadSpec loadSpec,
List<Option> options, MessageLog messageLog, Program program, TaskMonitor monitor) List<Option> options, MessageLog messageLog, Program program, TaskMonitor monitor)
throws IOException, CancelledException; throws IOException, LoadException, CancelledException;
@Override @Override
public final List<DomainObject> load(ByteProvider provider, String name, DomainFolder folder, public final LoadResults<? extends DomainObject> load(ByteProvider provider, String loadedName,
LoadSpec loadSpec, List<Option> options, MessageLog messageLog, Object consumer, Project project, String projectFolderPath, LoadSpec loadSpec, List<Option> options,
TaskMonitor monitor) throws IOException, CancelledException, InvalidNameException, MessageLog messageLog, Object consumer, TaskMonitor monitor) throws IOException,
DuplicateNameException, VersionException { CancelledException, VersionException, LoadException {
if (!isOverrideMainProgramName()) {
folder = ProjectDataUtils.createDomainFolderPath(folder, name);
}
List<DomainObject> results = new ArrayList<>();
if (!loadSpec.isComplete()) { if (!loadSpec.isComplete()) {
return results; throw new LoadException("Load spec is incomplete");
} }
List<LoadedProgram> loadedPrograms = List<Loaded<Program>> loadedPrograms = loadProgram(provider, loadedName, project,
loadProgram(provider, name, folder, loadSpec, options, messageLog, consumer, monitor); projectFolderPath, loadSpec, options, messageLog, consumer, monitor);
boolean success = false; boolean success = false;
try { try {
for (Loaded<Program> loadedProgram : loadedPrograms) {
monitor.checkCanceled(); monitor.checkCanceled();
for (LoadedProgram loadedProgram : loadedPrograms) { Program program = loadedProgram.getDomainObject();
monitor.checkCanceled();
Program program = loadedProgram.program();
applyProcessorLabels(options, program); applyProcessorLabels(options, program);
program.setEventsEnabled(true); program.setEventsEnabled(true);
// TODO: null should not be used as a determinant for saving; don't allow null
// folders?
if (loadedProgram.destinationFolder() == null) {
results.add(program);
continue;
}
String domainFileName = program.getName();
if (isOverrideMainProgramName()) {
// If this is the main imported program, use the given name, otherwise, use the
// internal program name. The first program in the list is the main imported program
if (program == loadedPrograms.get(0).program()) {
domainFileName = name;
}
}
if (createProgramFile(program, loadedProgram.destinationFolder(), domainFileName,
messageLog, monitor)) {
results.add(program);
}
else {
program.release(consumer); // some kind of exception happened; see MessageLog
}
} }
// Subclasses can perform custom post-load fix-ups // Subclasses can perform custom post-load fix-ups
postLoadProgramFixups(loadedPrograms, options, messageLog, monitor); postLoadProgramFixups(loadedPrograms, project, options, messageLog, monitor);
success = true; success = true;
return new LoadResults<Program>(loadedPrograms);
} }
finally { finally {
if (!success) { if (!success) {
release(loadedPrograms, consumer); release(loadedPrograms, consumer);
} }
postLoadCleanup(success);
} }
return results;
}
/**
* Some loaders can return more than one program.
* This method indicates whether the first (or main) program's name
* should be overridden and changed to the imported file name.
* @return true if first program name should be changed
*/
protected boolean isOverrideMainProgramName() {
return true;
} }
@Override @Override
public final boolean loadInto(ByteProvider provider, LoadSpec loadSpec, List<Option> options, public final void loadInto(ByteProvider provider, LoadSpec loadSpec, List<Option> options,
MessageLog messageLog, Program program, TaskMonitor monitor) MessageLog messageLog, Program program, TaskMonitor monitor)
throws IOException, CancelledException { throws IOException, LoadException, CancelledException {
if (!loadSpec.isComplete()) { if (!loadSpec.isComplete()) {
return false; throw new LoadException("Load spec is incomplete");
} }
program.setEventsEnabled(false); program.setEventsEnabled(false);
int transactionID = program.startTransaction("Loading - " + getName()); int transactionID = program.startTransaction("Loading - " + getName());
boolean success = false; boolean success = false;
try { try {
success = loadProgramInto(provider, loadSpec, options, messageLog, program, monitor); loadProgramInto(provider, loadSpec, options, messageLog, program, monitor);
return success; success = true;
} }
finally { finally {
program.endTransaction(transactionID, success); program.endTransaction(transactionID, success);
@ -237,19 +207,35 @@ public abstract class AbstractProgramLoader implements Loader {
} }
/** /**
* This gets called after the given list of {@link LoadedProgram programs}s is finished loading. * This gets called after the given list of {@link Loaded loaded programs}s is finished loading.
* It provides subclasses an opportunity to do follow-on actions to the load. * It provides subclasses an opportunity to do follow-on actions to the load.
* *
* @param loadedPrograms The {@link LoadedProgram programs} that got loaded. * @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 options The load options. * @param options The load options.
* @param messageLog The message log. * @param messageLog The message log.
* @param monitor A cancelable task monitor. * @param monitor A cancelable task monitor.
* @throws IOException if there was an IO-related problem loading. * @throws IOException if there was an IO-related problem loading.
* @throws CancelledException if the user cancelled the load. * @throws CancelledException if the user cancelled the load.
*/ */
protected void postLoadProgramFixups(List<LoadedProgram> loadedPrograms, List<Option> options, protected void postLoadProgramFixups(List<Loaded<Program>> loadedPrograms, Project project,
MessageLog messageLog, TaskMonitor monitor) throws CancelledException, IOException { List<Option> options, MessageLog messageLog, TaskMonitor monitor)
// Default behavior is to do nothing. throws CancelledException, IOException {
// Default behavior is to do nothing
}
/**
* This gets called as the final step of the load process. Subclasses may override it to ensure
* any resources they created can be cleaned up after the load finishes.
* <p>
* NOTE: Subclasses should not use this method to release any {@link Program}s they created when
* failure occurs. That should be done by the subclass as soon as it detects failure has
* occurred.
*
* @param success True if the load completed successfully; otherwise, false
*/
protected void postLoadCleanup(boolean success) {
// Default behavior is to do nothing
} }
/** /**
@ -262,6 +248,35 @@ public abstract class AbstractProgramLoader implements Loader {
return false; return false;
} }
/**
* Concatenates the given path elements to form a single path. Empty and null path elements
* are ignored.
*
* @param pathElements The path elements to append to one another
* @return A single path consisting of the given path elements appended together
*/
protected String concatenatePaths(String... pathElements) {
StringBuilder sb = new StringBuilder();
for (String pathElement : pathElements) {
if (pathElement == null || pathElement.isEmpty()) {
continue;
}
boolean sbEndsWithSlash =
sb.length() > 0 && "/\\".indexOf(sb.charAt(sb.length() - 1)) != -1;
boolean elementStartsWithSlash = "/\\".indexOf(pathElement.charAt(0)) != -1;
if (!sbEndsWithSlash && !elementStartsWithSlash && sb.length() > 0) {
sb.append("/");
}
else if (elementStartsWithSlash && sbEndsWithSlash) {
pathElement = pathElement.substring(1);
}
sb.append(pathElement);
}
return sb.toString();
}
/** /**
* Generates a block name. * Generates a block name.
* *
@ -307,25 +322,27 @@ public abstract class AbstractProgramLoader implements Loader {
Program prog = new ProgramDB(programName, language, compilerSpec, consumer); Program prog = new ProgramDB(programName, language, compilerSpec, consumer);
prog.setEventsEnabled(false); prog.setEventsEnabled(false);
int id = prog.startTransaction("Set program properties"); int id = prog.startTransaction("Set program properties");
boolean success = false;
try { try {
setProgramProperties(prog, provider, executableFormatName); setProgramProperties(prog, provider, executableFormatName);
if (shouldSetImageBase(prog, imageBase)) {
try { try {
if (shouldSetImageBase(prog, imageBase)) {
prog.setImageBase(imageBase, true); prog.setImageBase(imageBase, true);
} }
catch (AddressOverflowException e) { success = true;
// can't happen here return prog;
}
catch (LockException e) {
// can't happen here
} }
catch (LockException | AddressOverflowException e) {
// shouldn't ever happen here
throw new IOException(e);
} }
} }
finally { finally {
prog.endTransaction(id, true); prog.endTransaction(id, true); // More efficient to commit when program will be discarded
if (!success) {
prog.release(consumer);
}
} }
return prog;
} }
/** /**
@ -453,62 +470,17 @@ public abstract class AbstractProgramLoader implements Loader {
} }
/** /**
* Releases the given consumer from each of the provided {@link LoadedProgram}s. * Releases the given consumer from each of the provided {@link Loaded loaded programs}
* *
* @param loadedPrograms A list of {@link LoadedProgram}s which are no longer being used. * @param loadedPrograms A list of {@link Loaded loaded programs} which are no longer being used
* @param consumer The consumer that was marking the {@link DomainObject}s as being used. * @param consumer The consumer that was marking the {@link Program}s as being used
*/ */
protected final void release(List<LoadedProgram> loadedPrograms, Object consumer) { protected final void release(List<Loaded<Program>> loadedPrograms, Object consumer) {
for (LoadedProgram loadedProgram : loadedPrograms) { for (Loaded<Program> loadedProgram : loadedPrograms) {
loadedProgram.program().release(consumer); loadedProgram.getDomainObject().release(consumer);
} }
} }
private boolean createProgramFile(Program program, DomainFolder programFolder,
String programName, MessageLog messageLog, TaskMonitor monitor)
throws CancelledException, InvalidNameException {
int uniqueNameIndex = 0;
String uniqueName = programName;
while (!monitor.isCancelled()) {
try {
programFolder.createFile(uniqueName, program, monitor);
break;
}
catch (DuplicateFileException e) {
uniqueName = programName + uniqueNameIndex;
++uniqueNameIndex;
}
catch (CancelledException | InvalidNameException e) {
throw e;
}
catch (Exception e) {
Throwable t = e.getCause();
if (t == null) {
t = e;
}
String msg = t.getMessage();
if (msg == null) {
msg = "";
}
else {
msg = "\n" + msg;
}
Msg.showError(this, null, "Create Program Failed",
"Failed to create program file: " + uniqueName + msg, e);
messageLog.appendMsg("Unexpected exception creating file: " + uniqueName);
messageLog.appendException(e);
return false;
}
}
// makes the data tree expand to show new file!
// The following line was disabled as it causes UI updates that are better
// done by the callers to this loader instead of this loader.
//programFolder.setActive();
return true;
}
private void applyProcessorLabels(List<Option> options, Program program) { private void applyProcessorLabels(List<Option> options, Program program) {
int id = program.startTransaction("Finalize load"); int id = program.startTransaction("Finalize load");
try { try {

View file

@ -21,7 +21,7 @@ import java.util.List;
import ghidra.app.util.Option; import ghidra.app.util.Option;
import ghidra.app.util.bin.ByteProvider; import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.importer.MessageLog; import ghidra.app.util.importer.MessageLog;
import ghidra.framework.model.DomainFolder; import ghidra.framework.model.Project;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
import ghidra.program.model.lang.*; import ghidra.program.model.lang.*;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
@ -52,9 +52,10 @@ public abstract class AbstractProgramWrapperLoader extends AbstractProgramLoader
throws CancelledException, IOException; throws CancelledException, IOException;
@Override @Override
protected List<LoadedProgram> loadProgram(ByteProvider provider, String programName, protected List<Loaded<Program>> loadProgram(ByteProvider provider, String programName,
DomainFolder programFolder, LoadSpec loadSpec, List<Option> options, MessageLog log, Project project, String programFolderPath, LoadSpec loadSpec, List<Option> options,
Object consumer, TaskMonitor monitor) throws CancelledException, IOException { MessageLog log, Object consumer, TaskMonitor monitor)
throws IOException, CancelledException {
LanguageCompilerSpecPair pair = loadSpec.getLanguageCompilerSpec(); LanguageCompilerSpecPair pair = loadSpec.getLanguageCompilerSpec();
Language language = getLanguageService().getLanguage(pair.languageID); Language language = getLanguageService().getLanguage(pair.languageID);
@ -66,6 +67,8 @@ public abstract class AbstractProgramWrapperLoader extends AbstractProgramLoader
Program program = createProgram(provider, programName, imageBaseAddr, getName(), language, Program program = createProgram(provider, programName, imageBaseAddr, getName(), language,
compilerSpec, consumer); compilerSpec, consumer);
List<Loaded<Program>> loadedList =
List.of(new Loaded<Program>(program, programName, programFolderPath));
int transactionID = program.startTransaction("Loading"); int transactionID = program.startTransaction("Loading");
boolean success = false; boolean success = false;
@ -73,31 +76,31 @@ public abstract class AbstractProgramWrapperLoader extends AbstractProgramLoader
load(provider, loadSpec, options, program, monitor, log); load(provider, loadSpec, options, program, monitor, log);
createDefaultMemoryBlocks(program, language, log); createDefaultMemoryBlocks(program, language, log);
success = true; success = true;
return List.of(new LoadedProgram(program, programFolder)); return loadedList;
} }
finally { finally {
program.endTransaction(transactionID, success); program.endTransaction(transactionID, true); // More efficient to commit when program will be discarded
if (!success) { if (!success) {
program.release(consumer); release(loadedList, consumer);
} }
} }
} }
@Override @Override
protected boolean loadProgramInto(ByteProvider provider, LoadSpec loadSpec, protected void loadProgramInto(ByteProvider provider, LoadSpec loadSpec,
List<Option> options, MessageLog log, Program program, TaskMonitor monitor) List<Option> options, MessageLog log, Program program, TaskMonitor monitor)
throws CancelledException, IOException { throws CancelledException, LoadException, IOException {
LanguageCompilerSpecPair pair = loadSpec.getLanguageCompilerSpec(); LanguageCompilerSpecPair pair = loadSpec.getLanguageCompilerSpec();
LanguageID languageID = program.getLanguageID(); LanguageID languageID = program.getLanguageID();
CompilerSpecID compilerSpecID = program.getCompilerSpec().getCompilerSpecID(); CompilerSpecID compilerSpecID = program.getCompilerSpec().getCompilerSpecID();
if (!(pair.languageID.equals(languageID) && pair.compilerSpecID.equals(compilerSpecID))) { if (!(pair.languageID.equals(languageID) && pair.compilerSpecID.equals(compilerSpecID))) {
log.appendMsg(provider.getAbsolutePath() + String message = provider.getAbsolutePath() +
" does not have the same language/compiler spec as program " + program.getName()); " does not have the same language/compiler spec as program " + program.getName();
return false; log.appendMsg(message);
throw new LoadException(message);
} }
load(provider, loadSpec, options, program, monitor, log); load(provider, loadSpec, options, program, monitor, log);
return true;
} }
@Override @Override

View file

@ -21,8 +21,8 @@ import java.util.*;
import ghidra.app.util.*; import ghidra.app.util.*;
import ghidra.app.util.bin.ByteProvider; import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.importer.MessageLog; import ghidra.app.util.importer.MessageLog;
import ghidra.framework.model.DomainFolder;
import ghidra.framework.model.DomainObject; import ghidra.framework.model.DomainObject;
import ghidra.framework.model.Project;
import ghidra.program.database.mem.FileBytes; import ghidra.program.database.mem.FileBytes;
import ghidra.program.model.address.*; import ghidra.program.model.address.*;
import ghidra.program.model.lang.*; import ghidra.program.model.lang.*;
@ -269,9 +269,10 @@ public class BinaryLoader extends AbstractProgramLoader {
} }
@Override @Override
protected List<LoadedProgram> loadProgram(ByteProvider provider, String programName, protected List<Loaded<Program>> loadProgram(ByteProvider provider, String programName,
DomainFolder programFolder, LoadSpec loadSpec, List<Option> options, MessageLog log, Project project, String programFolderPath, LoadSpec loadSpec, List<Option> options,
Object consumer, TaskMonitor monitor) throws IOException, CancelledException { MessageLog log, Object consumer, TaskMonitor monitor)
throws IOException, CancelledException {
LanguageCompilerSpecPair pair = loadSpec.getLanguageCompilerSpec(); LanguageCompilerSpecPair pair = loadSpec.getLanguageCompilerSpec();
Language importerLanguage = getLanguageService().getLanguage(pair.languageID); Language importerLanguage = getLanguageService().getLanguage(pair.languageID);
CompilerSpec importerCompilerSpec = CompilerSpec importerCompilerSpec =
@ -281,30 +282,27 @@ public class BinaryLoader extends AbstractProgramLoader {
importerLanguage.getAddressFactory().getDefaultAddressSpace().getAddress(0); importerLanguage.getAddressFactory().getDefaultAddressSpace().getAddress(0);
Program prog = createProgram(provider, programName, baseAddr, getName(), importerLanguage, Program prog = createProgram(provider, programName, baseAddr, getName(), importerLanguage,
importerCompilerSpec, consumer); importerCompilerSpec, consumer);
List<Loaded<Program>> loadedList =
List.of(new Loaded<>(prog, programName, programFolderPath));
boolean success = false; boolean success = false;
try { try {
success = loadInto(provider, loadSpec, options, log, prog, monitor); loadInto(provider, loadSpec, options, log, prog, monitor);
if (success) {
createDefaultMemoryBlocks(prog, importerLanguage, log); createDefaultMemoryBlocks(prog, importerLanguage, log);
} success = true;
return loadedList;
} }
finally { finally {
if (!success) { if (!success) {
prog.release(consumer); release(loadedList, consumer);
prog = null;
} }
} }
List<LoadedProgram> results = new ArrayList<>();
if (prog != null) {
results.add(new LoadedProgram(prog, programFolder));
}
return results;
} }
@Override @Override
protected boolean loadProgramInto(ByteProvider provider, LoadSpec loadSpec, protected void loadProgramInto(ByteProvider provider, LoadSpec loadSpec,
List<Option> options, MessageLog log, Program prog, TaskMonitor monitor) List<Option> options, MessageLog log, Program prog, TaskMonitor monitor)
throws IOException, CancelledException { throws IOException, LoadException, CancelledException {
long length = getLength(options); long length = getLength(options);
//File file = provider.getFile(); //File file = provider.getFile();
long fileOffset = getFileOffset(options); long fileOffset = getFileOffset(options);
@ -329,12 +327,10 @@ public class BinaryLoader extends AbstractProgramLoader {
blockName = generateBlockName(prog, isOverlay, baseAddr.getAddressSpace()); blockName = generateBlockName(prog, isOverlay, baseAddr.getAddressSpace());
} }
createBlock(prog, isOverlay, blockName, baseAddr, fileBytes, length, log); createBlock(prog, isOverlay, blockName, baseAddr, fileBytes, length, log);
return true;
} }
catch (AddressOverflowException e) { catch (AddressOverflowException e) {
throw new IllegalArgumentException("Invalid address range specified: start:" + throw new LoadException("Invalid address range specified: start:" + baseAddr +
baseAddr + ", length:" + length + " - end address exceeds address space boundary!"); ", length:" + length + " - end address exceeds address space boundary!");
} }
} }

View file

@ -176,22 +176,16 @@ public class CoffLoader extends AbstractLibrarySupportLoader {
FileBytes fileBytes = MemoryBlockUtils.createFileBytes(program, provider, monitor); FileBytes fileBytes = MemoryBlockUtils.createFileBytes(program, provider, monitor);
int id = program.startTransaction("loading program from COFF");
boolean success = false;
try { try {
processSectionHeaders(provider, header, program, fileBytes, monitor, log, sectionsMap, processSectionHeaders(provider, header, program, fileBytes, monitor, log, sectionsMap,
performFakeLinking); performFakeLinking);
processSymbols(header, program, monitor, log, sectionsMap, symbolsMap); processSymbols(header, program, monitor, log, sectionsMap, symbolsMap);
processEntryPoint(header, program, monitor, log); processEntryPoint(header, program, monitor, log);
processRelocations(header, program, sectionsMap, symbolsMap, log, monitor); processRelocations(header, program, sectionsMap, symbolsMap, log, monitor);
success = true;
} }
catch (AddressOverflowException e) { catch (AddressOverflowException e) {
throw new IOException(e); throw new IOException(e);
} }
finally {
program.endTransaction(id, success);
}
} }
private void processEntryPoint(CoffFileHeader header, Program program, TaskMonitor monitor, private void processEntryPoint(CoffFileHeader header, Program program, TaskMonitor monitor,

View file

@ -24,6 +24,7 @@ import ghidra.app.util.bin.format.elf.ElfException;
import ghidra.app.util.bin.format.elf.ElfHeader; import ghidra.app.util.bin.format.elf.ElfHeader;
import ghidra.app.util.importer.MessageLog; import ghidra.app.util.importer.MessageLog;
import ghidra.framework.model.DomainObject; import ghidra.framework.model.DomainObject;
import ghidra.framework.model.Project;
import ghidra.framework.options.Options; import ghidra.framework.options.Options;
import ghidra.program.model.lang.Endian; import ghidra.program.model.lang.Endian;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
@ -152,14 +153,12 @@ public class ElfLoader extends AbstractLibrarySupportLoader {
} }
@Override @Override
protected void postLoadProgramFixups(List<LoadedProgram> loadedPrograms, List<Option> options, protected void postLoadProgramFixups(List<Loaded<Program>> loadedPrograms, Project project,
MessageLog messageLog, TaskMonitor monitor) throws CancelledException, IOException { List<Option> options, MessageLog messageLog, TaskMonitor monitor)
super.postLoadProgramFixups(loadedPrograms, options, messageLog, monitor); throws CancelledException, IOException {
super.postLoadProgramFixups(loadedPrograms, project, options, messageLog, monitor);
for (LoadedProgram loadedProgram : loadedPrograms) { ELFExternalSymbolResolver.fixUnresolvedExternalSymbols(loadedPrograms, messageLog, monitor);
ELFExternalSymbolResolver.fixUnresolvedExternalSymbols(loadedProgram.program(), true,
messageLog, monitor);
}
} }
@Override @Override

View file

@ -20,17 +20,25 @@ import java.util.*;
import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.FilenameUtils;
import db.DBConstants;
import db.DBHandle;
import ghidra.app.util.Option; import ghidra.app.util.Option;
import ghidra.app.util.bin.ByteProvider; import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.importer.MessageLog; import ghidra.app.util.importer.MessageLog;
import ghidra.framework.model.*; import ghidra.framework.model.DomainObject;
import ghidra.framework.model.Project;
import ghidra.framework.store.db.PackedDatabase;
import ghidra.framework.store.local.ItemSerializer; import ghidra.framework.store.local.ItemSerializer;
import ghidra.program.database.DataTypeArchiveContentHandler;
import ghidra.program.database.DataTypeArchiveDB;
import ghidra.program.model.data.FileDataTypeManager; import ghidra.program.model.data.FileDataTypeManager;
import ghidra.program.model.lang.LanguageNotFoundException;
import ghidra.program.model.listing.DataTypeArchive; import ghidra.program.model.listing.DataTypeArchive;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
import ghidra.util.InvalidNameException; import ghidra.util.exception.CancelledException;
import ghidra.util.exception.*; import ghidra.util.exception.VersionException;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
import utilities.util.FileUtilities;
/** /**
* Loads a packed Ghidra data type archive. * Loads a packed Ghidra data type archive.
@ -44,48 +52,66 @@ public class GdtLoader implements Loader {
} }
@Override @Override
public List<DomainObject> load(ByteProvider provider, String filename, public LoadResults<? extends DomainObject> load(ByteProvider provider, String filename,
DomainFolder programFolder, LoadSpec loadSpec, List<Option> options, Project project, String projectFolderPath, LoadSpec loadSpec, List<Option> options,
MessageLog messageLog, Object consumer, TaskMonitor monitor) throws IOException, MessageLog messageLog, Object consumer, TaskMonitor monitor) throws IOException,
CancelledException, DuplicateNameException, InvalidNameException, VersionException { CancelledException, VersionException {
DomainFile df = doImport(provider, filename, programFolder, monitor); DataTypeArchive dtArchive =
loadPackedProgramDatabase(provider, filename, consumer, monitor);
monitor.setMessage("Opening " + filename); return new LoadResults<>(dtArchive, filename, projectFolderPath);
// Allow upgrade since imported project archives must always be upgraded
DomainObject dobj = df.getDomainObject(consumer, true, false, monitor);
if (!(dobj instanceof DataTypeArchive)) {
if (dobj != null) {
dobj.release(consumer);
df.delete();
}
throw new IOException("File imported is not a Data Type Archive: " + filename);
} }
List<DomainObject> results = new ArrayList<DomainObject>(); private DataTypeArchive loadPackedProgramDatabase(ByteProvider provider, String programName,
results.add(dobj); Object consumer, TaskMonitor monitor)
return results; throws IOException, CancelledException, VersionException, LanguageNotFoundException {
} DataTypeArchive dtArchive;
private DomainFile doImport(ByteProvider provider, String filename,
DomainFolder programFolder, TaskMonitor monitor)
throws InvalidNameException, CancelledException, IOException {
File file = provider.getFile(); File file = provider.getFile();
DomainFolder folder = programFolder; File tmpFile = null;
if (file == null) {
file = tmpFile = createTmpFile(provider, monitor);
}
monitor.setMessage("Restoring " + file.getName()); try {
PackedDatabase packedDatabase = PackedDatabase.getPackedDatabase(file, true, monitor);
boolean success = false;
DBHandle dbh = null;
try {
if (!DataTypeArchiveContentHandler.DATA_TYPE_ARCHIVE_CONTENT_TYPE.equals(
packedDatabase.getContentType())) {
throw new IOException("File imported is not a Program: " + programName);
}
DomainFile df = folder.createFile(filename, file, monitor); monitor.setMessage("Restoring " + provider.getName());
return df; dbh = packedDatabase.open(monitor);
dtArchive = new DataTypeArchiveDB(dbh, DBConstants.UPGRADE, monitor, consumer);
success = true;
}
finally {
if (!success) {
if (dbh != null) {
dbh.close(); // also disposes packed database object
}
else {
packedDatabase.dispose();
}
}
}
return dtArchive;
}
finally {
if (tmpFile != null) {
tmpFile.delete();
}
}
} }
@Override @Override
public boolean loadInto(ByteProvider provider, LoadSpec loadSpec, List<Option> options, public void loadInto(ByteProvider provider, LoadSpec loadSpec, List<Option> options,
MessageLog messageLog, Program program, TaskMonitor monitor) MessageLog messageLog, Program program, TaskMonitor monitor)
throws IOException, CancelledException { throws IOException, LoadException, CancelledException {
throw new UnsupportedOperationException("cannot add GDT to program"); throw new LoadException("Cannot add GDT to program");
} }
@Override @Override
@ -110,6 +136,16 @@ public class GdtLoader implements Loader {
return FilenameUtils.removeExtension(provider.getName()); return FilenameUtils.removeExtension(provider.getName());
} }
private static File createTmpFile(ByteProvider provider, TaskMonitor monitor)
throws IOException {
File tmpFile = File.createTempFile("ghidra_gdt_loader", null);
try (InputStream is = provider.getInputStream(0);
FileOutputStream fos = new FileOutputStream(tmpFile)) {
FileUtilities.copyStreamToStream(is, fos, monitor);
}
return tmpFile;
}
private static boolean isGDTFile(ByteProvider provider) { private static boolean isGDTFile(ByteProvider provider) {
if (!provider.getName().toLowerCase().endsWith(FileDataTypeManager.SUFFIX)) { if (!provider.getName().toLowerCase().endsWith(FileDataTypeManager.SUFFIX)) {
return false; return false;

View file

@ -15,9 +15,8 @@
*/ */
package ghidra.app.util.opinion; package ghidra.app.util.opinion;
import java.util.*;
import java.io.*; import java.io.*;
import java.util.*;
import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.FilenameUtils;
@ -26,15 +25,16 @@ import db.DBHandle;
import ghidra.app.util.Option; import ghidra.app.util.Option;
import ghidra.app.util.bin.ByteProvider; import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.importer.MessageLog; import ghidra.app.util.importer.MessageLog;
import ghidra.framework.model.*; import ghidra.framework.model.DomainObject;
import ghidra.framework.model.Project;
import ghidra.framework.store.db.PackedDatabase; import ghidra.framework.store.db.PackedDatabase;
import ghidra.framework.store.local.ItemSerializer; import ghidra.framework.store.local.ItemSerializer;
import ghidra.program.database.ProgramContentHandler; import ghidra.program.database.ProgramContentHandler;
import ghidra.program.database.ProgramDB; import ghidra.program.database.ProgramDB;
import ghidra.program.model.lang.LanguageNotFoundException; import ghidra.program.model.lang.LanguageNotFoundException;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
import ghidra.util.InvalidNameException; import ghidra.util.exception.CancelledException;
import ghidra.util.exception.*; import ghidra.util.exception.VersionException;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
import utilities.util.FileUtilities; import utilities.util.FileUtilities;
@ -70,42 +70,19 @@ public class GzfLoader implements Loader {
} }
@Override @Override
public List<DomainObject> load(ByteProvider provider, String programName, public LoadResults<? extends DomainObject> load(ByteProvider provider, String programName,
DomainFolder programFolder, LoadSpec loadSpec, List<Option> options, Project project, String projectFolderPath, LoadSpec loadSpec, List<Option> options,
MessageLog messageLog, Object consumer, TaskMonitor monitor) throws IOException, MessageLog messageLog, Object consumer, TaskMonitor monitor) throws IOException,
CancelledException, DuplicateNameException, InvalidNameException, VersionException { CancelledException, VersionException {
DomainObject dobj; Program program = loadPackedProgramDatabase(provider, programName, consumer, monitor);
if (programFolder == null) { return new LoadResults<>(program, programName, projectFolderPath);
dobj = loadPackedProgramDatabase(provider, programName, consumer, monitor);
}
else {
DomainFile df = doLoad(provider, programName, programFolder, monitor);
boolean success = false;
try {
if (!ProgramContentHandler.PROGRAM_CONTENT_TYPE.equals(df.getContentType())) {
throw new IOException("File imported is not a Program: " + programName);
}
monitor.setMessage("Opening " + programName);
dobj = df.getDomainObject(consumer, true, false, monitor);
success = true;
}
finally {
if (!success) {
df.delete();
}
}
} }
List<DomainObject> results = new ArrayList<>(); private Program loadPackedProgramDatabase(ByteProvider provider, String programName,
results.add(dobj);
return results;
}
private DomainObject loadPackedProgramDatabase(ByteProvider provider, String programName,
Object consumer, TaskMonitor monitor) Object consumer, TaskMonitor monitor)
throws IOException, CancelledException, VersionException, LanguageNotFoundException { throws IOException, CancelledException, VersionException, LanguageNotFoundException {
DomainObject dobj; Program program;
File file = provider.getFile(); File file = provider.getFile();
File tmpFile = null; File tmpFile = null;
if (file == null) { if (file == null) {
@ -125,7 +102,7 @@ public class GzfLoader implements Loader {
monitor.setMessage("Restoring " + provider.getName()); monitor.setMessage("Restoring " + provider.getName());
dbh = packedDatabase.open(monitor); dbh = packedDatabase.open(monitor);
dobj = new ProgramDB(dbh, DBConstants.UPGRADE, monitor, consumer); program = new ProgramDB(dbh, DBConstants.UPGRADE, monitor, consumer);
success = true; success = true;
} }
finally { finally {
@ -138,7 +115,7 @@ public class GzfLoader implements Loader {
} }
} }
} }
return dobj; return program;
} }
finally { finally {
if (tmpFile != null) { if (tmpFile != null) {
@ -148,10 +125,10 @@ public class GzfLoader implements Loader {
} }
@Override @Override
public boolean loadInto(ByteProvider provider, LoadSpec loadSpec, List<Option> options, public void loadInto(ByteProvider provider, LoadSpec loadSpec, List<Option> options,
MessageLog messageLog, Program program, TaskMonitor monitor) MessageLog messageLog, Program program, TaskMonitor monitor)
throws IOException, CancelledException { throws IOException, LoadException, CancelledException {
throw new UnsupportedOperationException("cannot add GZF to program"); throw new LoadException("Cannot add GZF to program");
} }
@Override @Override
@ -169,30 +146,6 @@ public class GzfLoader implements Loader {
return FilenameUtils.removeExtension(provider.getName()); return FilenameUtils.removeExtension(provider.getName());
} }
private DomainFile doLoad(ByteProvider provider, String programName,
DomainFolder programFolder, TaskMonitor monitor)
throws InvalidNameException, CancelledException, IOException {
File file = provider.getFile();
File tmpFile = null;
if (file == null) {
file = tmpFile = createTmpFile(provider, monitor);
}
DomainFolder folder = programFolder;
monitor.setMessage("Restoring " + provider.getName());
try {
DomainFile df = folder.createFile(programName, file, monitor);
return df;
}
finally {
if (tmpFile != null) {
tmpFile.delete();
}
}
}
private static File createTmpFile(ByteProvider provider, TaskMonitor monitor) private static File createTmpFile(ByteProvider provider, TaskMonitor monitor)
throws IOException { throws IOException {
File tmpFile = File.createTempFile("ghidra_gzf_loader", null); File tmpFile = File.createTempFile("ghidra_gzf_loader", null);

View file

@ -21,8 +21,8 @@ import java.util.*;
import ghidra.app.util.Option; import ghidra.app.util.Option;
import ghidra.app.util.bin.ByteProvider; import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.importer.MessageLog; import ghidra.app.util.importer.MessageLog;
import ghidra.framework.model.DomainFolder;
import ghidra.framework.model.DomainObject; import ghidra.framework.model.DomainObject;
import ghidra.framework.model.Project;
import ghidra.program.model.address.*; import ghidra.program.model.address.*;
import ghidra.program.model.lang.*; import ghidra.program.model.lang.*;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
@ -142,9 +142,10 @@ public class IntelHexLoader extends AbstractProgramLoader {
} }
@Override @Override
protected List<LoadedProgram> loadProgram(ByteProvider provider, String programName, protected List<Loaded<Program>> loadProgram(ByteProvider provider, String programName,
DomainFolder programFolder, LoadSpec loadSpec, List<Option> options, MessageLog log, Project project, String programFolderPath, LoadSpec loadSpec, List<Option> options,
Object consumer, TaskMonitor monitor) throws IOException, CancelledException { MessageLog log, Object consumer, TaskMonitor monitor)
throws IOException, CancelledException {
LanguageCompilerSpecPair pair = loadSpec.getLanguageCompilerSpec(); LanguageCompilerSpecPair pair = loadSpec.getLanguageCompilerSpec();
Language importerLanguage = getLanguageService().getLanguage(pair.languageID); Language importerLanguage = getLanguageService().getLanguage(pair.languageID);
CompilerSpec importerCompilerSpec = CompilerSpec importerCompilerSpec =
@ -152,45 +153,38 @@ public class IntelHexLoader extends AbstractProgramLoader {
Program prog = createProgram(provider, programName, null, getName(), importerLanguage, Program prog = createProgram(provider, programName, null, getName(), importerLanguage,
importerCompilerSpec, consumer); importerCompilerSpec, consumer);
List<Loaded<Program>> loadedList =
List.of(new Loaded<>(prog, programName, programFolderPath));
boolean success = false; boolean success = false;
try { try {
success = loadInto(provider, loadSpec, options, log, prog, monitor); loadInto(provider, loadSpec, options, log, prog, monitor);
if (success) {
createDefaultMemoryBlocks(prog, importerLanguage, log); createDefaultMemoryBlocks(prog, importerLanguage, log);
} success = true;
return loadedList;
} }
finally { finally {
if (!success) { if (!success) {
prog.release(consumer); release(loadedList, consumer);
prog = null;
} }
} }
List<LoadedProgram> results = new ArrayList<>();
if (prog != null) {
results.add(new LoadedProgram(prog, programFolder));
}
return results;
} }
@Override @Override
protected boolean loadProgramInto(ByteProvider provider, LoadSpec loadSpec, protected void loadProgramInto(ByteProvider provider, LoadSpec loadSpec,
List<Option> options, MessageLog log, Program prog, TaskMonitor monitor) List<Option> options, MessageLog log, Program prog, TaskMonitor monitor)
throws IOException, CancelledException { throws IOException, LoadException, CancelledException {
Address baseAddr = getBaseAddr(options); Address baseAddr = getBaseAddr(options);
if (baseAddr == null) { if (baseAddr == null) {
baseAddr = prog.getAddressFactory().getDefaultAddressSpace().getAddress(0); baseAddr = prog.getAddressFactory().getDefaultAddressSpace().getAddress(0);
} }
boolean success = false;
try { try {
processIntelHex(provider, options, log, prog, monitor); processIntelHex(provider, options, log, prog, monitor);
success = true;
} }
catch (AddressOverflowException e) { catch (AddressOverflowException e) {
throw new IOException( throw new LoadException(
"Hex file specifies range greater than allowed address space - " + e.getMessage()); "Hex file specifies range greater than allowed address space - " + e.getMessage());
} }
return success;
} }
private void processIntelHex(ByteProvider provider, List<Option> options, MessageLog log, private void processIntelHex(ByteProvider provider, List<Option> options, MessageLog log,

View file

@ -0,0 +1,40 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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}
* fails in an expected way. The supplied message should explain the reason.
*/
public class LoadException extends IOException {
/**
* Create a new {@link LoadException} with the given message
*
* @param message The exception message
*/
public LoadException(String message) {
super(message);
}
}

View file

@ -0,0 +1,186 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.util.opinion;
import java.io.IOException;
import java.util.*;
import java.util.function.Predicate;
import ghidra.app.util.importer.MessageLog;
import ghidra.framework.model.*;
import ghidra.util.Msg;
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.
*
* @param <T> The type of {@link DomainObject}s that were loaded
*/
public class LoadResults<T extends DomainObject> implements Iterable<Loaded<T>> {
private final List<Loaded<T>> loadedList;
/**
* Creates a new {@link LoadResults} that contains the given non-empty {@link List} of
* {@link Loaded} {@link DomainObject}s. The first entry in the {@link List} is assumed to be
* the {@link #getPrimary() primary} {@link Loaded} {@link DomainObject}.
*
* @param loadedList A {@link List} of {@link Loaded} {@link DomainObject}s
* @throws IllegalArgumentException if the provided {@link List} is null or empty
*/
public LoadResults(List<Loaded<T>> loadedList) throws IllegalArgumentException {
if (loadedList == null || loadedList.isEmpty()) {
throw new IllegalArgumentException("The loaded list must not be empty");
}
this.loadedList = new ArrayList<>(loadedList);
}
/**
* Creates a 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}.
*
* @param domainObject The loaded {@link DomainObject}
* @param name The name of the loaded {@link DomainObject}. If a
* {@link #save(Project, Object, MessageLog, TaskMonitor) save} occurs, this will attempted to
* be used for the resulting {@link DomainFile}'s name.
* @param projectFolderPath The project folder path this will get saved to during a
* {@link #save(Project, Object, MessageLog, TaskMonitor) save} operation. If null or empty,
* the root project folder will be used.
*/
public LoadResults(T domainObject, String name, String projectFolderPath) {
this(List.of(new Loaded<T>(domainObject, name, projectFolderPath)));
}
/**
* Gets the "primary" {@link Loaded} {@link DomainObject}, who's meaning is defined by each
* {@link Loader} implementation
*
* @return The "primary" {@link Loaded} {@link DomainObject}
*/
public Loaded<T> getPrimary() {
return loadedList.get(0);
}
/**
* Gets the "primary" {@link DomainObject}, who's meaning is defined by each {@link Loader}
* implementation
*
* @return The "primary" {@link DomainObject}
*/
public T getPrimaryDomainObject() {
return loadedList.get(0).getDomainObject();
}
/**
* Gets the number of {@link Loaded} {@link DomainObject}s in this {@link LoadResults}. The
* size will always be greater than 0.
*
* @return The number of {@link Loaded} {@link DomainObject}s in this {@link LoadResults}
*/
public int size() {
return loadedList.size();
}
/**
* {@link Loaded#save(Project, MessageLog, TaskMonitor) Saves} each {@link Loaded}
* {@link DomainObject} to the given {@link Project}.
* <p>
* NOTE: If any fail to save, none will be saved (already saved {@link DomainFile}s will be
* cleaned up/deleted), and all {@link Loaded} {@link DomainObject}s will have been
* {@link #release(Object) released}.
*
* @param project The {@link Project} to save to
* @param consumer the consumer
* @param messageLog The log
* @param monitor A cancelable task monitor
* @throws CancelledException if the operation was cancelled
* @throws IOException If there was a problem saving
* @see Loaded#save(Project, MessageLog, TaskMonitor)
*/
public void save(Project project, Object consumer, MessageLog messageLog, TaskMonitor monitor)
throws CancelledException, IOException {
boolean success = false;
try {
for (Loaded<T> loaded : loadedList) {
loaded.save(project, messageLog, monitor);
}
success = true;
}
finally {
if (!success) {
for (Loaded<T> loaded : this) {
try {
loaded.release(consumer);
loaded.deleteSavedDomainFile(consumer);
}
catch (IOException e1) {
Msg.error(getClass(), "Failed to delete: " + loaded);
}
}
}
}
}
/**
* Notify all of the {@link Loaded} {@link DomainObject}s that the specified consumer is no
* longer using them. When the last consumer invokes this method, the {@link Loaded}
* {@link DomainObject}s will be closed and will become invalid.
*
* @param consumer the consumer
*/
public void release(Object consumer) {
loadedList.forEach(loaded -> loaded.release(consumer));
}
/**
* Notify the filtered {@link Loaded} {@link DomainObject}s that the specified consumer is no
* longer using them. When the last consumer invokes this method, the filtered {@link Loaded}
* {@link DomainObject}s will be closed and will become invalid.
*
* @param consumer the consumer
* @param filter a filter to apply to the {@link Loaded} {@link DomainObject}s prior to the
* release
*/
public void release(Object consumer, Predicate<? super Loaded<T>> filter) {
loadedList.stream().filter(filter).forEach(loaded -> loaded.release(consumer));
}
/**
* Notify the non-primary {@link Loaded} {@link DomainObject}s that the specified consumer is no
* longer using them. When the last consumer invokes this method, the non-primary {@link Loaded}
* {@link DomainObject}s will be closed and will become invalid.
*
* @param consumer the consumer
*/
public void releaseNonPrimary(Object consumer) {
for (int i = 0; i < loadedList.size(); i++) {
if (i > 0) {
loadedList.get(i).release(consumer);
}
}
}
@Override
public Iterator<Loaded<T>> iterator() {
return loadedList.iterator();
}
}

View file

@ -0,0 +1,221 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.util.opinion;
import java.io.FileNotFoundException;
import java.io.IOException;
import ghidra.app.util.importer.MessageLog;
import ghidra.framework.model.*;
import ghidra.util.InvalidNameException;
import ghidra.util.exception.*;
import ghidra.util.task.TaskMonitor;
/**
* A loaded {@link DomainObject} produced by a {@link Loader}. In addition to storing the loaded
* {@link DomainObject}, it also stores the {@link Loader}'s desired name and project folder path
* for the loaded {@link DomainObject}, should it get saved to a project.
*
* @param <T> The type of {@link DomainObject} that was loaded
*/
public class Loaded<T extends DomainObject> {
private final T domainObject;
private final String name;
private String projectFolderPath;
private DomainFile domainFile;
/**
* Creates a new {@link Loaded} object
*
* @param domainObject The loaded {@link DomainObject}
* @param name The name of the loaded {@link DomainObject}. If a
* {@link #save(Project, MessageLog, TaskMonitor)} occurs, this will attempted to be used for
* the resulting {@link DomainFile}'s name.
* @param projectFolderPath The project folder path this will get saved to during a
* {@link #save(Project, MessageLog, TaskMonitor)} operation. If null or empty, the root
* project folder will be used.
*/
public Loaded(T domainObject, String name, String projectFolderPath) {
this.domainObject = domainObject;
this.name = name;
setProjectFolderPath(projectFolderPath);
}
/**
* Gets the loaded {@link DomainObject}
*
* @return The loaded {@link DomainObject}
*/
public T getDomainObject() {
return domainObject;
}
/**
* Gets the name of the loaded {@link DomainObject}. If a
* {@link #save(Project, MessageLog, TaskMonitor)} occurs, this will attempted to be used for
* the resulting {@link DomainFile}'s name.
*
* @return the name of the loaded {@link DomainObject}
*/
public String getName() {
return name;
}
/**
* Gets the project folder path this will get saved to during a
* {@link #save(Project, MessageLog, TaskMonitor)} operation.
* <p>
* NOTE: The returned path will always end with a "/".
*
* @return the project folder path
*/
public String getProjectFolderPath() {
return projectFolderPath;
}
/**
* Sets the project folder path this will get saved to during a
* {@link #save(Project, MessageLog, TaskMonitor)} operation.
*
* @param projectFolderPath The project folder path this will get saved to during a
* {@link #save(Project, MessageLog, TaskMonitor)} operation. If null or empty, the root
* project folder will be used.
*/
public void setProjectFolderPath(String projectFolderPath) {
if (projectFolderPath == null) {
projectFolderPath = "/";
}
else if (!projectFolderPath.endsWith("/")) {
projectFolderPath += "/";
}
this.projectFolderPath = projectFolderPath;
}
/**
* Notify the loaded {@link DomainObject} that the specified consumer is no longer using it.
* When the last consumer invokes this method, the loaded {@link DomainObject} will be closed
* and will become invalid.
*
* @param consumer the consumer
*/
public void release(Object consumer) {
if (!domainObject.isClosed() && domainObject.isUsedBy(consumer)) {
domainObject.release(consumer);
}
}
/**
* Saves the loaded {@link DomainObject} to the given {@link Project} at this object's
* project folder path, using this object's name.
* <p>
* If a {@link DomainFile} already exists with the same desired name and project folder path,
* the desired name will get a counter value appended to it to avoid a naming conflict.
* Therefore, it should not be assumed that the returned {@link DomainFile} will have the same
* name as a call to {@link #getName()}.
*
* @param project The {@link Project} to save to
* @param messageLog The log
* @param monitor A cancelable task monitor
* @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
*/
public DomainFile save(Project project, MessageLog messageLog, TaskMonitor monitor)
throws CancelledException, ClosedException, IOException {
if (domainObject.isClosed()) {
throw new ClosedException(
"Cannot saved closed DomainObject: " + domainObject.getName());
}
try {
if (getSavedDomainFile() != null) { //
throw new IOException("Already saved to " + domainFile);
}
}
catch (FileNotFoundException e) {
// DomainFile was already saved, but no longer exists.
// Allow the save to proceeded.
domainFile = null;
}
int uniqueNameIndex = 0;
String uniqueName = name;
try {
DomainFolder programFolder = ProjectDataUtils.createDomainFolderPath(
project.getProjectData().getRootFolder(), projectFolderPath);
while (!monitor.isCancelled()) {
try {
domainFile = programFolder.createFile(uniqueName, domainObject, monitor);
return domainFile;
}
catch (DuplicateFileException e) {
uniqueName = name + "." + uniqueNameIndex;
++uniqueNameIndex;
}
}
}
catch (InvalidNameException e) {
throw new IOException(e);
}
throw new CancelledException();
}
/**
* Gets the loaded {@link DomainObject}'s associated {@link DomainFile} that was
* {@link #save(Project, MessageLog, TaskMonitor) saved}
*
* @return The loaded {@link DomainObject}'s associated saved {@link DomainFile}, or null if
* was not saved
* @throws FileNotFoundException If the loaded {@link DomainObject} was saved but the associated
* {@link DomainFile} no longer exists
* @see #save(Project, MessageLog, TaskMonitor)
*/
public DomainFile getSavedDomainFile() throws FileNotFoundException {
if (domainFile != null && !domainFile.exists()) {
throw new FileNotFoundException("Saved DomainFile no longer exists: " + domainFile);
}
return domainFile;
}
/**
* Deletes the loaded {@link DomainObject}'s associated {@link DomainFile} that was
* {@link #save(Project, MessageLog, TaskMonitor) saved}. This method has no effect if it was
* never saved.
* <p>
* NOTE: The loaded {@link DomainObject} must be {@link #release(Object) released} prior to
* calling this method.
*
* @param consumer the consumer
* @throws IOException If there was an issue deleting the saved {@link DomainFile}
* @see #save(Project, MessageLog, TaskMonitor)
*/
void deleteSavedDomainFile(Object consumer) throws IOException {
if (domainFile != null && domainFile.exists()) {
domainFile.delete();
domainFile = null;
}
}
@Override
public String toString() {
return getProjectFolderPath() + getName();
}
}

View file

@ -23,13 +23,12 @@ import ghidra.app.util.Option;
import ghidra.app.util.bin.ByteProvider; import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.importer.MessageLog; import ghidra.app.util.importer.MessageLog;
import ghidra.formats.gfilesystem.FSRL; import ghidra.formats.gfilesystem.FSRL;
import ghidra.framework.model.DomainFolder; import ghidra.framework.model.*;
import ghidra.framework.model.DomainObject;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
import ghidra.util.InvalidNameException;
import ghidra.util.classfinder.ClassSearcher; import ghidra.util.classfinder.ClassSearcher;
import ghidra.util.classfinder.ExtensionPoint; import ghidra.util.classfinder.ExtensionPoint;
import ghidra.util.exception.*; import ghidra.util.exception.CancelledException;
import ghidra.util.exception.VersionException;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
/** /**
@ -41,8 +40,17 @@ import ghidra.util.task.TaskMonitor;
*/ */
public interface Loader extends ExtensionPoint, Comparable<Loader> { public interface Loader extends ExtensionPoint, Comparable<Loader> {
/**
* A string prefixed to each loader headless command line argument to avoid naming conflicts
* with other headless command line argument names
*/
public static final String COMMAND_LINE_ARG_PREFIX = "-loader"; public static final String COMMAND_LINE_ARG_PREFIX = "-loader";
/**
* Key used to lookup and store all loader options in the project's saved state
*/
public static final String OPTIONS_PROJECT_SAVE_STATE_KEY = "LOADER_OPTIONS";
/** /**
* If this {@link Loader} supports loading the given {@link ByteProvider}, this methods returns * 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 * a {@link Collection} of all supported {@link LoadSpec}s that contain discovered load
@ -59,33 +67,50 @@ public interface Loader extends ExtensionPoint, Comparable<Loader> {
public Collection<LoadSpec> findSupportedLoadSpecs(ByteProvider provider) throws IOException; public Collection<LoadSpec> findSupportedLoadSpecs(ByteProvider provider) throws IOException;
/** /**
* Loads bytes in a particular format as a new {@link DomainObject}. * Loads bytes in a particular format as a new {@link Loaded} {@link DomainObject}. Multiple
* Multiple {@link DomainObject}s may end up getting created, depending on the nature of the * {@link DomainObject}s may end up getting created, depending on the nature of the format.
* format. * The {@link Loaded} {@link DomainObject}s are bundled together in a {@link LoadResults}
* object which provides convenience methods to operate on the entire group of {@link Loaded}
* {@link DomainObject}s.
* <p>
* Note that when the load completes, the returned {@link Loaded} {@link DomainObject}s are not
* saved to a project. That is the responsibility of the caller (see
* {@link LoadResults#save(Project, Object, MessageLog, TaskMonitor)}).
* <p>
* It is also the responsibility of the caller to release the returned {@link Loaded}
* {@link DomainObject}s with {@link LoadResults#release(Object)} when they are no longer
* needed.
* *
* @param provider The bytes to load. * @param provider The bytes to load.
* @param name The name of the thing that's being loaded. * @param loadedName A suggested name for the primary {@link Loaded} {@link DomainObject}.
* @param folder The {@link DomainFolder} where the loaded thing should be saved. Could be * This is just a suggestion, and a {@link Loader} implementation reserves the right to change
* null if the thing should not be pre-saved. * 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 loadSpec The {@link LoadSpec} to use during load.
* @param options The load options. * @param options The load options.
* @param messageLog The message log. * @param messageLog The message log.
* @param consumer A consumer object for {@link DomainObject} generated. * @param consumer A consumer object for generated {@link DomainObject}s.
* @param monitor A cancelable task monitor. * @param monitor A task monitor.
* @return A list of loaded {@link DomainObject}s (element 0 corresponds to primary loaded * @return The {@link LoadResults} which contains one or more {@link Loaded}
* object). * {@link DomainObject}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 IOException if there was an IO-related problem loading.
* @throws CancelledException if the user cancelled the load. * @throws CancelledException if the user cancelled the load.
* @throws DuplicateNameException if the load resulted in a naming conflict with the * @throws VersionException if the load process tried to open an existing {@link DomainFile}
* {@link DomainObject}. * which was created with a newer or unsupported version of Ghidra
* @throws InvalidNameException if an invalid {@link DomainObject} name was used during load.
* @throws VersionException if there was an issue with database versions, probably due to a
* failed language upgrade.
*/ */
public List<DomainObject> load(ByteProvider provider, String name, DomainFolder folder, public LoadResults<? extends DomainObject> load(ByteProvider provider, String loadedName,
LoadSpec loadSpec, List<Option> options, MessageLog messageLog, Object consumer, Project project, String projectFolderPath, LoadSpec loadSpec, List<Option> options,
TaskMonitor monitor) throws IOException, CancelledException, DuplicateNameException, MessageLog messageLog, Object consumer, TaskMonitor monitor) throws IOException,
InvalidNameException, VersionException; CancelledException, VersionException, LoadException;
/** /**
* Loads bytes into the specified {@link Program}. This method will not create any new * Loads bytes into the specified {@link Program}. This method will not create any new
@ -97,13 +122,13 @@ public interface Loader extends ExtensionPoint, Comparable<Loader> {
* @param messageLog The message log. * @param messageLog The message log.
* @param program The {@link Program} to load into. * @param program The {@link Program} to load into.
* @param monitor A cancelable task monitor. * @param monitor A cancelable task monitor.
* @return True if the file was successfully loaded; otherwise, false. * @throws LoadException if the load failed in an expected way.
* @throws IOException if there was an IO-related problem loading. * @throws IOException if there was an IO-related problem loading.
* @throws CancelledException if the user cancelled the load. * @throws CancelledException if the user cancelled the load.
*/ */
public boolean loadInto(ByteProvider provider, LoadSpec loadSpec, List<Option> options, public void loadInto(ByteProvider provider, LoadSpec loadSpec, List<Option> options,
MessageLog messageLog, Program program, TaskMonitor monitor) MessageLog messageLog, Program program, TaskMonitor monitor)
throws IOException, CancelledException; throws IOException, LoadException, CancelledException;
/** /**
* Gets the default {@link Loader} options. * Gets the default {@link Loader} options.
@ -187,6 +212,17 @@ public interface Loader extends ExtensionPoint, Comparable<Loader> {
return false; return false;
} }
/**
* Checks to see if this {@link Loader} loads into a new {@link DomainFolder} instead of a new
* {@link DomainFile}
*
* @return True if this {@link Loader} loads into a new {@link DomainFolder} instead of a new
* {@link DomainFile}
*/
public default boolean loadsIntoNewFolder() {
return false;
}
@Override @Override
public default int compareTo(Loader o) { public default int compareTo(Loader o) {
int compareTiers = getTier().compareTo(o.getTier()); int compareTiers = getTier().compareTo(o.getTier());

View file

@ -22,8 +22,8 @@ import ghidra.app.util.MemoryBlockUtils;
import ghidra.app.util.Option; import ghidra.app.util.Option;
import ghidra.app.util.bin.ByteProvider; import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.importer.MessageLog; import ghidra.app.util.importer.MessageLog;
import ghidra.framework.model.DomainFolder;
import ghidra.framework.model.DomainObject; import ghidra.framework.model.DomainObject;
import ghidra.framework.model.Project;
import ghidra.program.model.address.*; import ghidra.program.model.address.*;
import ghidra.program.model.lang.*; import ghidra.program.model.lang.*;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
@ -160,9 +160,10 @@ public class MotorolaHexLoader extends AbstractProgramLoader {
} }
@Override @Override
protected List<LoadedProgram> loadProgram(ByteProvider provider, String programName, protected List<Loaded<Program>> loadProgram(ByteProvider provider, String programName,
DomainFolder programFolder, LoadSpec loadSpec, List<Option> options, MessageLog log, Project project, String programFolderPath, LoadSpec loadSpec, List<Option> options,
Object consumer, TaskMonitor monitor) throws IOException, CancelledException { MessageLog log, Object consumer, TaskMonitor monitor)
throws IOException, LoadException, CancelledException {
LanguageCompilerSpecPair pair = loadSpec.getLanguageCompilerSpec(); LanguageCompilerSpecPair pair = loadSpec.getLanguageCompilerSpec();
Language importerLanguage = getLanguageService().getLanguage(pair.languageID); Language importerLanguage = getLanguageService().getLanguage(pair.languageID);
CompilerSpec importerCompilerSpec = CompilerSpec importerCompilerSpec =
@ -170,45 +171,38 @@ public class MotorolaHexLoader extends AbstractProgramLoader {
Program prog = createProgram(provider, programName, null, getName(), importerLanguage, Program prog = createProgram(provider, programName, null, getName(), importerLanguage,
importerCompilerSpec, consumer); importerCompilerSpec, consumer);
List<Loaded<Program>> loadedList =
List.of(new Loaded<>(prog, programName, programFolderPath));
boolean success = false; boolean success = false;
try { try {
success = loadInto(provider, loadSpec, options, log, prog, monitor); loadInto(provider, loadSpec, options, log, prog, monitor);
if (success) {
createDefaultMemoryBlocks(prog, importerLanguage, log); createDefaultMemoryBlocks(prog, importerLanguage, log);
} success = true;
return loadedList;
} }
finally { finally {
if (!success) { if (!success) {
prog.release(consumer); release(loadedList, consumer);
prog = null;
} }
} }
List<LoadedProgram> results = new ArrayList<>();
if (prog != null) {
results.add(new LoadedProgram(prog, programFolder));
}
return results;
} }
@Override @Override
protected boolean loadProgramInto(ByteProvider provider, LoadSpec loadSpec, protected void loadProgramInto(ByteProvider provider, LoadSpec loadSpec,
List<Option> options, MessageLog log, Program prog, TaskMonitor monitor) List<Option> options, MessageLog log, Program prog, TaskMonitor monitor)
throws IOException, CancelledException { throws IOException, LoadException, CancelledException {
Address baseAddr = getBaseAddr(options); Address baseAddr = getBaseAddr(options);
if (baseAddr == null) { if (baseAddr == null) {
baseAddr = prog.getAddressFactory().getDefaultAddressSpace().getAddress(0); baseAddr = prog.getAddressFactory().getDefaultAddressSpace().getAddress(0);
} }
boolean success = false;
try { try {
processMotorolaHex(provider, options, prog, baseAddr, monitor); processMotorolaHex(provider, options, prog, baseAddr, monitor);
success = true;
} }
catch (AddressOverflowException e) { catch (AddressOverflowException e) {
throw new IOException( throw new LoadException(
"Hex file specifies range greater than allowed address space - " + e.getMessage()); "Hex file specifies range greater than allowed address space - " + e.getMessage());
} }
return success;
} }
private void processMotorolaHex(ByteProvider provider, List<Option> options, Program program, private void processMotorolaHex(ByteProvider provider, List<Option> options, Program program,

View file

@ -132,21 +132,15 @@ public class OmfLoader extends AbstractProgramWrapperLoader {
// the original bytes. // the original bytes.
MemoryBlockUtils.createFileBytes(program, provider, monitor); MemoryBlockUtils.createFileBytes(program, provider, monitor);
int id = program.startTransaction("loading program from OMF");
boolean success = false;
try { try {
processSegmentHeaders(reader, header, program, monitor, log); processSegmentHeaders(reader, header, program, monitor, log);
processExternalSymbols(header, program, monitor, log); processExternalSymbols(header, program, monitor, log);
processPublicSymbols(header, program, monitor, log); processPublicSymbols(header, program, monitor, log);
processRelocations(header, program, monitor, log); processRelocations(header, program, monitor, log);
success = true;
} }
catch (AddressOverflowException e) { catch (AddressOverflowException e) {
throw new IOException(e); throw new IOException(e);
} }
finally {
program.endTransaction(id, success);
}
} }
/** /**

View file

@ -29,8 +29,8 @@ import ghidra.app.util.OptionException;
import ghidra.app.util.bin.ByteProvider; import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.importer.MessageLog; import ghidra.app.util.importer.MessageLog;
import ghidra.app.util.xml.*; import ghidra.app.util.xml.*;
import ghidra.framework.model.DomainFolder;
import ghidra.framework.model.DomainObject; import ghidra.framework.model.DomainObject;
import ghidra.framework.model.Project;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
import ghidra.program.model.lang.*; import ghidra.program.model.lang.*;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
@ -177,10 +177,11 @@ public class XmlLoader extends AbstractProgramLoader {
} }
@Override @Override
protected List<LoadedProgram> loadProgram(ByteProvider provider, String programName, protected List<Loaded<Program>> loadProgram(ByteProvider provider, String programName,
DomainFolder programFolder, LoadSpec loadSpec, List<Option> options, MessageLog log, Project project, String programFolderPath, LoadSpec loadSpec, List<Option> options,
Object consumer, TaskMonitor monitor) throws IOException, CancelledException { MessageLog log, Object consumer, TaskMonitor monitor)
List<LoadedProgram> results = new ArrayList<>(); throws IOException, LoadException, CancelledException {
List<Loaded<Program>> results = new ArrayList<>();
LanguageCompilerSpecPair pair = loadSpec.getLanguageCompilerSpec(); LanguageCompilerSpecPair pair = loadSpec.getLanguageCompilerSpec();
Language importerLanguage = getLanguageService().getLanguage(pair.languageID); Language importerLanguage = getLanguageService().getLanguage(pair.languageID);
@ -198,36 +199,35 @@ public class XmlLoader extends AbstractProgramLoader {
} }
Program prog = createProgram(provider, programName, imageBase, getName(), importerLanguage, Program prog = createProgram(provider, programName, imageBase, getName(), importerLanguage,
importerCompilerSpec, consumer); importerCompilerSpec, consumer);
List<Loaded<Program>> loadedList =
List.of(new Loaded<>(prog, programName, programFolderPath));
boolean success = false; boolean success = false;
try { try {
success = doImport(result.lastXmlMgr, options, log, prog, monitor, false); success = doImport(result.lastXmlMgr, options, log, prog, monitor, false);
if (success) { if (success) {
createDefaultMemoryBlocks(prog, importerLanguage, log); createDefaultMemoryBlocks(prog, importerLanguage, log);
return loadedList;
} }
throw new LoadException("Failed to load");
} }
finally { finally {
if (!success) { if (!success) {
prog.release(consumer); release(loadedList, consumer);
prog = null;
} }
} }
if (prog != null) {
results.add(new LoadedProgram(prog, programFolder));
}
return results;
} }
@Override @Override
protected boolean loadProgramInto(ByteProvider provider, LoadSpec loadSpec, protected void loadProgramInto(ByteProvider provider, LoadSpec loadSpec,
List<Option> options, MessageLog log, Program prog, TaskMonitor monitor) List<Option> options, MessageLog log, Program prog, TaskMonitor monitor)
throws IOException, CancelledException { throws IOException, LoadException, CancelledException {
File file = provider.getFile(); File file = provider.getFile();
return doImport(new ProgramXmlMgr(file), options, log, prog, monitor, true); doImport(new ProgramXmlMgr(file), options, log, prog, monitor, true);
} }
private boolean doImportWork(final ProgramXmlMgr mgr, final List<Option> options, private boolean doImportWork(final ProgramXmlMgr mgr, final List<Option> options,
final MessageLog log, Program prog, TaskMonitor monitor, final MessageLog log, Program prog, TaskMonitor monitor,
final boolean isAddToProgram) throws IOException { final boolean isAddToProgram) throws LoadException {
MessageLog mgrLog = null; MessageLog mgrLog = null;
boolean success = false; boolean success = false;
try { try {
@ -247,7 +247,7 @@ public class XmlLoader extends AbstractProgramLoader {
message = log.toString(); message = log.toString();
} }
Msg.warn(this, "XML import exception, log: " + message, e); Msg.warn(this, "XML import exception, log: " + message, e);
throw new IOException(e.getMessage(), e); throw new LoadException(e.getMessage());
} }
return success; return success;
} }

View file

@ -18,12 +18,12 @@ package ghidra.base.project;
import java.io.*; import java.io.*;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.nio.channels.OverlappingFileLockException; import java.nio.channels.OverlappingFileLockException;
import java.util.*; import java.util.HashMap;
import java.util.Iterator;
import ghidra.app.plugin.core.analysis.AutoAnalysisManager; import ghidra.app.plugin.core.analysis.AutoAnalysisManager;
import ghidra.app.util.importer.*; import ghidra.app.util.importer.*;
import ghidra.app.util.opinion.Loader; import ghidra.app.util.opinion.*;
import ghidra.app.util.opinion.LoaderService;
import ghidra.framework.Application; import ghidra.framework.Application;
import ghidra.framework.client.*; import ghidra.framework.client.*;
import ghidra.framework.cmd.Command; import ghidra.framework.cmd.Command;
@ -612,8 +612,10 @@ public class GhidraProject {
CompilerSpec compilerSpec) throws CancelledException, DuplicateNameException, CompilerSpec compilerSpec) throws CancelledException, DuplicateNameException,
InvalidNameException, VersionException, IOException { InvalidNameException, VersionException, IOException {
MessageLog messageLog = new MessageLog(); MessageLog messageLog = new MessageLog();
Program program = AutoImporter.importByLookingForLcs(file, domainFolder, language, LoadResults<Program> loadResults = AutoImporter.importByLookingForLcs(file, project,
compilerSpec, this, messageLog, MONITOR); domainFolder.getPathname(), language, compilerSpec, this, messageLog, MONITOR);
Program program = loadResults.getPrimaryDomainObject();
loadResults.releaseNonPrimary(this);
initializeProgram(program, false); initializeProgram(program, false);
return program; return program;
} }
@ -630,8 +632,10 @@ public class GhidraProject {
throws CancelledException, DuplicateNameException, InvalidNameException, throws CancelledException, DuplicateNameException, InvalidNameException,
VersionException, IOException { VersionException, IOException {
MessageLog messageLog = new MessageLog(); MessageLog messageLog = new MessageLog();
Program program = AutoImporter.importByUsingSpecificLoaderClass(file, null, loaderClass, LoadResults<Program> loadResults = AutoImporter.importByUsingSpecificLoaderClass(file,
null, this, messageLog, MONITOR); project, null, loaderClass, null, this, messageLog, MONITOR);
Program program = loadResults.getPrimaryDomainObject();
loadResults.releaseNonPrimary(this);
initializeProgram(program, false); initializeProgram(program, false);
return program; return program;
} }
@ -642,13 +646,11 @@ public class GhidraProject {
MessageLog messageLog = new MessageLog(); MessageLog messageLog = new MessageLog();
SingleLoaderFilter loaderFilter = new SingleLoaderFilter(loaderClass, null); SingleLoaderFilter loaderFilter = new SingleLoaderFilter(loaderClass, null);
LcsHintLoadSpecChooser opinionChoose = new LcsHintLoadSpecChooser(language, compilerSpec); LcsHintLoadSpecChooser opinionChoose = new LcsHintLoadSpecChooser(language, compilerSpec);
List<Program> programs = AutoImporter.importFresh(file, null, this, messageLog, MONITOR, LoadResults<Program> loadResults =
loaderFilter, opinionChoose, null, new LoaderArgsOptionChooser(loaderFilter), AutoImporter.importFresh(file, project, null, this, messageLog, MONITOR, loaderFilter,
MultipleProgramsStrategy.ONE_PROGRAM_OR_NULL); opinionChoose, null, new LoaderArgsOptionChooser(loaderFilter));
if (programs != null && programs.size() == 1) { loadResults.releaseNonPrimary(this);
return programs.get(0); return loadResults.getPrimaryDomainObject();
}
return null;
} }
public Program importProgram(File file) throws CancelledException, DuplicateNameException, public Program importProgram(File file) throws CancelledException, DuplicateNameException,
@ -659,8 +661,10 @@ public class GhidraProject {
public Program importProgram(File file, DomainFolder domainFolder) throws CancelledException, public Program importProgram(File file, DomainFolder domainFolder) throws CancelledException,
DuplicateNameException, InvalidNameException, VersionException, IOException { DuplicateNameException, InvalidNameException, VersionException, IOException {
MessageLog messageLog = new MessageLog(); MessageLog messageLog = new MessageLog();
Program program = LoadResults<Program> loadResults = AutoImporter.importByUsingBestGuess(file, project,
AutoImporter.importByUsingBestGuess(file, domainFolder, this, messageLog, MONITOR); domainFolder.getPathname(), this, messageLog, MONITOR);
Program program = loadResults.getPrimaryDomainObject();
loadResults.releaseNonPrimary(this);
initializeProgram(program, false); initializeProgram(program, false);
return program; return program;
} }
@ -696,14 +700,12 @@ public class GhidraProject {
MessageLog messageLog = new MessageLog(); MessageLog messageLog = new MessageLog();
String programNameOverride = null; String programNameOverride = null;
List<Program> programs = AutoImporter.importFresh(file, null, this, messageLog, MONITOR, LoadResults<Program> loadResults =
AutoImporter.importFresh(file, project, null, this, messageLog, MONITOR,
LoaderService.ACCEPT_ALL, LoadSpecChooser.CHOOSE_THE_FIRST_PREFERRED, LoaderService.ACCEPT_ALL, LoadSpecChooser.CHOOSE_THE_FIRST_PREFERRED,
programNameOverride, OptionChooser.DEFAULT_OPTIONS, programNameOverride, OptionChooser.DEFAULT_OPTIONS);
MultipleProgramsStrategy.ONE_PROGRAM_OR_NULL); loadResults.releaseNonPrimary(this);
if (programs != null && programs.size() == 1) { return loadResults.getPrimaryDomainObject();
return programs.get(0);
}
return null;
} }
//================================================================================================== //==================================================================================================

View file

@ -328,7 +328,10 @@ public class DataTreeDialog extends DialogComponentProvider
* @return null if there was no domain folder selected * @return null if there was no domain folder selected
*/ */
public DomainFolder getDomainFolder() { public DomainFolder getDomainFolder() {
if (domainFolder == null && !cancelled) { if (cancelled) {
return null;
}
if (domainFolder == null) {
domainFolder = treePanel.getSelectedDomainFolder(); domainFolder = treePanel.getSelectedDomainFolder();
} }
return domainFolder; return domainFolder;

View file

@ -53,13 +53,13 @@ public class AddToProgramDialog extends ImporterDialog {
super("Add To Program: " + fsrl.getPath(), tool, loaderMap, byteProvider, null); super("Add To Program: " + fsrl.getPath(), tool, loaderMap, byteProvider, null);
this.addToProgram = addToProgram; this.addToProgram = addToProgram;
folderNameTextField.setText(getFolderName(addToProgram)); folderNameTextField.setText(getFolderName(addToProgram));
filenameTextField.setText(addToProgram.getName()); nameTextField.setText(addToProgram.getName());
setSelectedLanguage(getLanguageSpec()); setSelectedLanguage(getLanguageSpec());
languageTextField.setEnabled(false); languageTextField.setEnabled(false);
folderNameTextField.setEnabled(false); folderNameTextField.setEnabled(false);
folderButton.setEnabled(false); folderButton.setEnabled(false);
languageButton.setEnabled(false); languageButton.setEnabled(false);
filenameTextField.setEnabled(false); nameTextField.setEnabled(false);
validateFormInput(); validateFormInput();
} }

View file

@ -73,7 +73,7 @@ public class ImporterDialog extends DialogComponentProvider {
private String suggestedDestinationPath; private String suggestedDestinationPath;
protected ByteProvider byteProvider; protected ByteProvider byteProvider;
protected JTextField filenameTextField; protected JTextField nameTextField;
private boolean userHasChangedName; private boolean userHasChangedName;
protected JButton folderButton; protected JButton folderButton;
protected JButton languageButton; protected JButton languageButton;
@ -90,7 +90,7 @@ public class ImporterDialog extends DialogComponentProvider {
* @param byteProvider the ByteProvider for getting the bytes from the file to be imported. The * @param byteProvider the ByteProvider for getting the bytes from the file to be imported. The
* dialog takes ownership of the ByteProvider and it will be closed when the dialog is closed * dialog takes ownership of the ByteProvider and it will be closed when the dialog is closed
* @param suggestedDestinationPath optional string path that will be pre-pended to the destination * @param suggestedDestinationPath optional string path that will be pre-pended to the destination
* filename. Any path specified in the destination filename field will be created when * name. Any path specified in the destination name field will be created when
* the user performs the import (as opposed to the {@link #setDestinationFolder(DomainFolder) destination folder} * the user performs the import (as opposed to the {@link #setDestinationFolder(DomainFolder) destination folder}
* option which requires the DomainFolder to already exist). The two destination paths work together * option which requires the DomainFolder to already exist). The two destination paths work together
* to specify the final Ghidra project folder where the imported binary is placed. * to specify the final Ghidra project folder where the imported binary is placed.
@ -160,26 +160,26 @@ public class ImporterDialog extends DialogComponentProvider {
panel.add(new GLabel("Destination Folder: ", SwingConstants.RIGHT)); panel.add(new GLabel("Destination Folder: ", SwingConstants.RIGHT));
panel.add(buildFolderPanel()); panel.add(buildFolderPanel());
panel.add(new GLabel("Program Name: ", SwingConstants.RIGHT)); panel.add(new GLabel("Program Name: ", SwingConstants.RIGHT));
panel.add(buildFilenameTextField()); panel.add(buildNameTextField());
return panel; return panel;
} }
private Component buildFilenameTextField() { private Component buildNameTextField() {
String initalSuggestedFilename = String initalSuggestedName =
FSUtilities.appendPath(suggestedDestinationPath, getSuggestedFilename()); FSUtilities.appendPath(suggestedDestinationPath, getSuggestedName());
int columns = (initalSuggestedFilename.length() > 50) ? 50 : 0; int columns = (initalSuggestedName.length() > 50) ? 50 : 0;
filenameTextField = new JTextField(initalSuggestedFilename, columns); nameTextField = new JTextField(initalSuggestedName, columns);
// Use a key listener to track users edits. We can't use the document listener, as // Use a key listener to track users edits. We can't use the document listener, as
// we change the name field ourselves when other fields are changed. // we change the name field ourselves when other fields are changed.
filenameTextField.addKeyListener(new KeyAdapter() { nameTextField.addKeyListener(new KeyAdapter() {
@Override @Override
public void keyTyped(KeyEvent e) { public void keyTyped(KeyEvent e) {
// tracking all key events; are there any that we don't want to track? // tracking all key events; are there any that we don't want to track?
userHasChangedName = true; userHasChangedName = true;
} }
}); });
filenameTextField.getDocument().addDocumentListener(new DocumentListener() { nameTextField.getDocument().addDocumentListener(new DocumentListener() {
@Override @Override
public void changedUpdate(DocumentEvent e) { public void changedUpdate(DocumentEvent e) {
// don't care // don't care
@ -195,10 +195,10 @@ public class ImporterDialog extends DialogComponentProvider {
validateFormInput(); validateFormInput();
} }
}); });
return filenameTextField; return nameTextField;
} }
private String getSuggestedFilename() { private String getSuggestedName() {
Loader loader = getSelectedLoader(); Loader loader = getSelectedLoader();
if (loader != null) { if (loader != null) {
return loader.getPreferredFileName(byteProvider); return loader.getPreferredFileName(byteProvider);
@ -302,9 +302,9 @@ public class ImporterDialog extends DialogComponentProvider {
if (loader != null) { if (loader != null) {
languageNeeded = isLanguageNeeded(loader); languageNeeded = isLanguageNeeded(loader);
setSelectedLanguage(getPreferredLanguage(loader)); setSelectedLanguage(getPreferredLanguage(loader));
String newSuggestedFilename = String newSuggestedName =
FSUtilities.appendPath(suggestedDestinationPath, getSuggestedFilename()); FSUtilities.appendPath(suggestedDestinationPath, getSuggestedName());
setFilename(newSuggestedFilename); setName(newSuggestedName);
} }
else { else {
languageNeeded = true; languageNeeded = true;
@ -459,7 +459,7 @@ public class ImporterDialog extends DialogComponentProvider {
return false; return false;
} }
optionsButton.setEnabled(selectedLanguage != null); optionsButton.setEnabled(selectedLanguage != null);
if (!validateFilename()) { if (!validateName()) {
return false; return false;
} }
setStatusText(""); setStatusText("");
@ -467,64 +467,67 @@ public class ImporterDialog extends DialogComponentProvider {
return true; return true;
} }
private boolean validateFilename() { private boolean validateName() {
Loader loader = getSelectedLoader();
boolean loadsIntoFolder = loader.loadsIntoNewFolder();
String destType = loadsIntoFolder ? "folder" : "file name";
if (getName().isEmpty()) { if (getName().isEmpty()) {
setStatusText("Please enter a destination file name."); setStatusText("Please enter a destination " + destType + ".");
return false; return false;
} }
if (warnedAboutInvalidFilenameChars()) { if (warnedAboutInvalidNameChars()) {
return false; return false;
} }
if (isMissingFilename()) { if (isMissingName()) {
setStatusText("Destination path does not specify filename."); setStatusText("Destination path does not specify " + destType + ".");
return false; return false;
} }
if (isDuplicateFilename()) { if (isDuplicateName(loadsIntoFolder)) {
setStatusText("Destination file name already exists."); setStatusText("Destination " + destType + " already exists.");
return false; return false;
} }
if (isFilenameTooLong()) { if (isNameTooLong()) {
setStatusText("Destination file name is too long. ( >" + setStatusText("Destination " + destType + " is too long. ( >" +
tool.getProject().getProjectData().getMaxNameLength() + ")"); tool.getProject().getProjectData().getMaxNameLength() + ")");
return false; return false;
} }
return true; return true;
} }
private boolean warnedAboutInvalidFilenameChars() { private boolean warnedAboutInvalidNameChars() {
String filename = getName(); String name = getName();
for (int i = 0; i < filename.length(); i++) { for (int i = 0; i < name.length(); i++) {
char ch = filename.charAt(i); char ch = name.charAt(i);
if (!LocalFileSystem.isValidNameCharacter(ch) && ch != '/') { if (!LocalFileSystem.isValidNameCharacter(ch) && ch != '/') {
setStatusText("Invalid character " + ch + " in filename."); setStatusText("Invalid character " + ch + " in name.");
return true; return true;
} }
} }
return false; return false;
} }
private boolean isMissingFilename() { private boolean isMissingName() {
String filename = FilenameUtils.getName(getName()); String name = FilenameUtils.getName(getName());
return StringUtils.isBlank(filename); return StringUtils.isBlank(name);
} }
private boolean isDuplicateFilename() { private boolean isDuplicateName(boolean isFolder) {
String pathFilename = getName(); String pathName = getName();
String parentPath = FilenameUtils.getFullPathNoEndSeparator(pathFilename); String parentPath = FilenameUtils.getFullPathNoEndSeparator(pathName);
String filename = FilenameUtils.getName(pathFilename); String fileOrFolderName = FilenameUtils.getName(pathName);
DomainFolder localDestFolder = DomainFolder localDestFolder =
(parentPath != null) ? ProjectDataUtils.lookupDomainPath(destinationFolder, parentPath) (parentPath != null) ? ProjectDataUtils.lookupDomainPath(destinationFolder, parentPath)
: destinationFolder; : destinationFolder;
if (localDestFolder != null) { if (localDestFolder != null) {
if (localDestFolder.getFolder(filename) != null || if (isFolder && localDestFolder.getFolder(fileOrFolderName) != null ||
localDestFolder.getFile(filename) != null) { !isFolder && localDestFolder.getFile(fileOrFolderName) != null) {
return true; return true;
} }
} }
return false; return false;
} }
private boolean isFilenameTooLong() { private boolean isNameTooLong() {
int maxNameLen = tool.getProject().getProjectData().getMaxNameLength(); int maxNameLen = tool.getProject().getProjectData().getMaxNameLength();
for (String pathPart : getName().split("/")) { for (String pathPart : getName().split("/")) {
if (pathPart.length() >= maxNameLen) { if (pathPart.length() >= maxNameLen) {
@ -535,17 +538,17 @@ public class ImporterDialog extends DialogComponentProvider {
} }
private String getName() { private String getName() {
return filenameTextField.getText().trim(); return nameTextField.getText().trim();
} }
private void setFilename(String s) { private void setName(String s) {
if (userHasChangedName && validateFilename()) { if (userHasChangedName && validateName()) {
// Changing the user's text is really annoying. Keep the user's filename, if it is valid // Changing the user's text is really annoying. Keep the user's name, if it is valid
return; return;
} }
filenameTextField.setText(s); nameTextField.setText(s);
filenameTextField.setCaretPosition(s.length()); nameTextField.setCaretPosition(s.length());
} }
protected void setSelectedLanguage(LanguageCompilerSpecPair lcsPair) { protected void setSelectedLanguage(LanguageCompilerSpecPair lcsPair) {
@ -599,8 +602,8 @@ public class ImporterDialog extends DialogComponentProvider {
return languageTextField; return languageTextField;
} }
JTextField getFilenameTextField() { JTextField getNameTextField() {
return filenameTextField; return nameTextField;
} }
} }

View file

@ -15,11 +15,10 @@
*/ */
package ghidra.plugin.importer; package ghidra.plugin.importer;
import java.util.*;
import java.awt.Window; import java.awt.Window;
import java.io.Closeable; import java.io.Closeable;
import java.io.IOException; import java.io.IOException;
import java.util.*;
import docking.widgets.OptionDialog; import docking.widgets.OptionDialog;
import ghidra.app.plugin.core.help.AboutDomainObjectUtils; import ghidra.app.plugin.core.help.AboutDomainObjectUtils;
@ -311,8 +310,19 @@ public class ImporterUtilities {
doFSImportHelper((GFileSystemProgramProvider) refdFile.fsRef.getFilesystem(), doFSImportHelper((GFileSystemProgramProvider) refdFile.fsRef.getFilesystem(),
gfile, destFolder, consumer, monitor); gfile, destFolder, consumer, monitor);
if (program != null) { if (program != null) {
doPostImportProcessing(tool, programManager, fsrl, Arrays.asList(program), LoadResults<? extends DomainObject> loadResults = new LoadResults<>(program,
consumer, "", monitor); program.getName(), destFolder.getPathname());
boolean success = false;
try {
doPostImportProcessing(tool, programManager, fsrl, loadResults, consumer,
"", monitor);
success = true;
}
finally {
if (!success) {
program.release(consumer);
}
}
} }
} }
catch (Exception e) { catch (Exception e) {
@ -331,16 +341,25 @@ public class ImporterUtilities {
Program program = Program program =
pfs.getProgram(gfile, DefaultLanguageService.getLanguageService(), monitor, consumer); pfs.getProgram(gfile, DefaultLanguageService.getLanguageService(), monitor, consumer);
if (program != null) { if (program == null) {
String importFilename = ProjectDataUtils.getUniqueName(destFolder, program.getName()); return null;
if (importFilename == null) {
program.release(consumer);
throw new IOException("Unable to find unique name for " + program.getName());
} }
destFolder.createFile(importFilename, program, monitor); boolean success = false;
try {
String importFilename = ProjectDataUtils.getUniqueName(destFolder, program.getName());
if (importFilename == null) {
throw new IOException("Unable to find unique name for " + program.getName());
} }
destFolder.createFile(importFilename, program, monitor);
success = true;
return program; return program;
}
finally {
if (!success) {
program.release(consumer);
}
}
} }
@ -365,13 +384,13 @@ public class ImporterUtilities {
Object consumer = new Object(); Object consumer = new Object();
MessageLog messageLog = new MessageLog(); MessageLog messageLog = new MessageLog();
List<DomainObject> importedObjects = loadSpec.getLoader().load(bp, programName, LoadResults<? extends DomainObject> loadResults = loadSpec.getLoader()
destFolder, loadSpec, options, messageLog, consumer, monitor); .load(bp, programName, tool.getProject(), destFolder.getPathname(), loadSpec,
if (importedObjects == null) { options, messageLog, consumer, monitor);
return;
}
doPostImportProcessing(tool, programManager, fsrl, importedObjects, consumer, loadResults.save(tool.getProject(), consumer, messageLog, monitor);
doPostImportProcessing(tool, programManager, fsrl, loadResults, consumer,
messageLog.toString(), monitor); messageLog.toString(), monitor);
} }
catch (CancelledException e) { catch (CancelledException e) {
@ -384,17 +403,16 @@ public class ImporterUtilities {
} }
private static Set<DomainFile> doPostImportProcessing(PluginTool pluginTool, private static Set<DomainFile> doPostImportProcessing(PluginTool pluginTool,
ProgramManager programManager, FSRL fsrl, List<DomainObject> importedObjects, ProgramManager programManager, FSRL fsrl,
Object consumer, String importMessages, TaskMonitor monitor) LoadResults<? extends DomainObject> loadResults, Object consumer, String importMessages,
throws CancelledException, IOException { TaskMonitor monitor) throws CancelledException {
boolean firstProgram = true; boolean firstProgram = true;
Set<DomainFile> importedFilesSet = new HashSet<>(); Set<DomainFile> importedFilesSet = new HashSet<>();
for (DomainObject importedObject : importedObjects) { for (Loaded<? extends DomainObject> loaded : loadResults) {
monitor.checkCanceled(); monitor.checkCanceled();
if (importedObject instanceof Program) { if (loaded.getDomainObject() instanceof Program program) {
Program program = (Program) importedObject;
ProgramMappingService.createAssociation(fsrl, program); ProgramMappingService.createAssociation(fsrl, program);
if (programManager != null) { if (programManager != null) {
@ -407,10 +425,10 @@ public class ImporterUtilities {
} }
if (firstProgram) { if (firstProgram) {
// currently we only show results for the imported program, not any libraries // currently we only show results for the imported program, not any libraries
displayResults(pluginTool, importedObject, importedObject.getDomainFile(), displayResults(pluginTool, loaded.getDomainObject(),
importMessages); loaded.getDomainObject().getDomainFile(), importMessages);
} }
importedObject.release(consumer); loaded.release(consumer);
firstProgram = false; firstProgram = false;
} }

View file

@ -15,9 +15,8 @@
*/ */
package ghidra.plugins.importer.tasks; package ghidra.plugins.importer.tasks;
import java.util.List;
import java.io.IOException; import java.io.IOException;
import java.util.List;
import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.FilenameUtils;
@ -26,8 +25,9 @@ import ghidra.app.services.ProgramManager;
import ghidra.app.util.Option; import ghidra.app.util.Option;
import ghidra.app.util.bin.ByteProvider; import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.importer.MessageLog; import ghidra.app.util.importer.MessageLog;
import ghidra.app.util.opinion.LoadSpec; import ghidra.app.util.opinion.*;
import ghidra.formats.gfilesystem.*; import ghidra.formats.gfilesystem.*;
import ghidra.framework.main.AppInfo;
import ghidra.framework.model.*; import ghidra.framework.model.*;
import ghidra.framework.store.local.LocalFileSystem; import ghidra.framework.store.local.LocalFileSystem;
import ghidra.plugin.importer.ProgramMappingService; import ghidra.plugin.importer.ProgramMappingService;
@ -36,7 +36,8 @@ import ghidra.plugins.importer.batch.BatchGroup.BatchLoadConfig;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
import ghidra.util.InvalidNameException; import ghidra.util.InvalidNameException;
import ghidra.util.Msg; import ghidra.util.Msg;
import ghidra.util.exception.*; import ghidra.util.exception.CancelledException;
import ghidra.util.exception.VersionException;
import ghidra.util.task.Task; import ghidra.util.task.Task;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
@ -146,20 +147,21 @@ public class ImportBatchTask extends Task {
Object consumer = new Object(); Object consumer = new Object();
try { try {
MessageLog messageLog = new MessageLog(); MessageLog messageLog = new MessageLog();
List<DomainObject> importedObjects = loadSpec.getLoader() Project project = AppInfo.getActiveProject();
.load(byteProvider, LoadResults<? extends DomainObject> loadResults = loadSpec.getLoader()
fixupProjectFilename(destInfo.second), destInfo.first, loadSpec, .load(byteProvider, fixupProjectFilename(destInfo.second), project,
destInfo.first.getPathname(), loadSpec,
getOptionsFor(batchLoadConfig, loadSpec, byteProvider), messageLog, getOptionsFor(batchLoadConfig, loadSpec, byteProvider), messageLog,
consumer, consumer, monitor);
monitor);
// TODO: accumulate batch results // TODO: accumulate batch results
if (importedObjects != null) { if (loadResults != null) {
try { try {
processImportResults(importedObjects, batchLoadConfig, monitor); loadResults.save(project, consumer, messageLog, monitor);
processImportResults(loadResults, batchLoadConfig, monitor);
} }
finally { finally {
releaseAll(importedObjects, consumer); loadResults.release(consumer);
} }
} }
totalAppsImported++; totalAppsImported++;
@ -173,8 +175,7 @@ public class ImportBatchTask extends Task {
catch (CancelledException e) { catch (CancelledException e) {
Msg.debug(this, "Batch Import cancelled"); Msg.debug(this, "Batch Import cancelled");
} }
catch (DuplicateNameException | InvalidNameException | VersionException catch (IOException | VersionException | IllegalArgumentException e) {
| IOException | IllegalArgumentException e) {
Msg.error(this, "Import failed for " + batchLoadConfig.getPreferredFileName(), e); Msg.error(this, "Import failed for " + batchLoadConfig.getPreferredFileName(), e);
} }
} }
@ -190,24 +191,15 @@ public class ImportBatchTask extends Task {
return sb.toString(); return sb.toString();
} }
private void releaseAll(List<DomainObject> importedObjects, Object consumer) {
for (DomainObject obj : importedObjects) {
if (obj.isUsedBy(consumer)) {
obj.release(consumer);
}
}
}
/* /*
* creates fsrl associations, updates task statistics, opens the imported program (if allowed) * creates fsrl associations, updates task statistics, opens the imported program (if allowed)
*/ */
private void processImportResults(List<DomainObject> importedObjects, BatchLoadConfig appInfo, private void processImportResults(LoadResults<? extends DomainObject> loadResults,
TaskMonitor monitor) throws CancelledException, IOException { BatchLoadConfig appInfo, TaskMonitor monitor) {
for (DomainObject obj : importedObjects) {
if (obj instanceof Program) {
Program program = (Program) obj;
for (Loaded<? extends DomainObject> loaded : loadResults) {
DomainObject obj = loaded.getDomainObject();
if (obj instanceof Program program) {
if (programManager != null && totalObjsImported < MAX_PROGRAMS_TO_OPEN) { if (programManager != null && totalObjsImported < MAX_PROGRAMS_TO_OPEN) {
programManager.openProgram(program, programManager.openProgram(program,
totalObjsImported == 0 ? ProgramManager.OPEN_CURRENT totalObjsImported == 0 ? ProgramManager.OPEN_CURRENT

View file

@ -26,6 +26,8 @@ import ghidra.app.plugin.core.analysis.EmbeddedMediaAnalyzer;
import ghidra.app.util.bin.*; import ghidra.app.util.bin.*;
import ghidra.app.util.importer.AutoImporter; import ghidra.app.util.importer.AutoImporter;
import ghidra.app.util.importer.MessageLog; import ghidra.app.util.importer.MessageLog;
import ghidra.app.util.opinion.LoadException;
import ghidra.app.util.opinion.LoadResults;
import ghidra.framework.Application; import ghidra.framework.Application;
import ghidra.framework.HeadlessGhidraApplicationConfiguration; import ghidra.framework.HeadlessGhidraApplicationConfiguration;
import ghidra.program.model.data.*; import ghidra.program.model.data.*;
@ -79,24 +81,34 @@ public class ProgramExaminer {
private ProgramExaminer(ByteProvider provider) throws GhidraException { private ProgramExaminer(ByteProvider provider) throws GhidraException {
initializeGhidra(); initializeGhidra();
messageLog = new MessageLog(); messageLog = new MessageLog();
LoadResults<Program> loadResults = null;
try { try {
program = AutoImporter.importByUsingBestGuess(provider, null, this, messageLog, try {
TaskMonitor.DUMMY); loadResults = AutoImporter.importByUsingBestGuess(provider, null, null, this,
if (program == null) {
program = AutoImporter.importAsBinary(provider, null, defaultLanguage, null, this,
messageLog, TaskMonitor.DUMMY); messageLog, TaskMonitor.DUMMY);
program = loadResults.getPrimaryDomainObject();
} }
if (program == null) { catch (LoadException e) {
try {
program = AutoImporter
.importAsBinary(provider, null, null, defaultLanguage, null, this,
messageLog, TaskMonitor.DUMMY)
.getDomainObject();
}
catch (LoadException e1) {
throw new GhidraException( throw new GhidraException(
"Can't create program from input: " + messageLog.toString()); "Can't create program from input: " + messageLog.toString());
} }
} }
}
catch (Exception e) { catch (Exception e) {
messageLog.appendException(e); messageLog.appendException(e);
throw new GhidraException(e); throw new GhidraException(e);
} }
finally { finally {
if (loadResults != null) {
loadResults.releaseNonPrimary(this);
}
try { try {
provider.close(); provider.close();
} }

View file

@ -17,10 +17,12 @@ package ghidra.program.util;
import java.io.IOException; import java.io.IOException;
import java.util.*; import java.util.*;
import java.util.stream.Collectors;
import ghidra.app.util.importer.MessageLog; import ghidra.app.util.importer.MessageLog;
import ghidra.app.util.opinion.ElfLoader; import ghidra.app.util.opinion.ElfLoader;
import ghidra.framework.model.*; import ghidra.app.util.opinion.Loaded;
import ghidra.framework.model.DomainObject;
import ghidra.framework.options.Options; import ghidra.framework.options.Options;
import ghidra.program.model.listing.*; import ghidra.program.model.listing.*;
import ghidra.program.model.symbol.*; import ghidra.program.model.symbol.*;
@ -41,23 +43,24 @@ public class ELFExternalSymbolResolver {
* already existing / imported libraries. * already existing / imported libraries.
* <p> * <p>
* *
* @param program ELF {@link Program} to fix. * @param loadedPrograms ELF {@link Loaded} {@link Program}s to fix..
* @param saveIfModified boolean flag, if true the program will be saved if there was a
* modification.
* @param messageLog {@link MessageLog} to write info message to. * @param messageLog {@link MessageLog} to write info message to.
* @param monitor {@link TaskMonitor} to watch for cancel and update with progress. * @param monitor {@link TaskMonitor} to watch for cancel and update with progress.
* @throws CancelledException if user cancels * @throws CancelledException if user cancels
* @throws IOException if error reading * @throws IOException if error reading
*/ */
public static void fixUnresolvedExternalSymbols(Program program, boolean saveIfModified, public static void fixUnresolvedExternalSymbols(List<Loaded<Program>> loadedPrograms,
MessageLog messageLog, TaskMonitor monitor) throws CancelledException, IOException { MessageLog messageLog, TaskMonitor monitor) throws CancelledException, IOException {
DomainFolder domainFolder = program.getDomainFile().getParent(); Map<String, Loaded<Program>> loadedByName = loadedPrograms.stream()
if (domainFolder == null) { .collect(
return; // headless case with nothing pre-saved...currently unsupported Collectors.toMap(loaded -> loaded.getName(), loaded -> loaded));
}
ProjectData projectData = domainFolder.getProjectData();
Collection<Long> unresolvedExternalFunctionIds = getUnresolvedExternalFunctionIds(program); monitor.initialize(loadedByName.size());
for (Loaded<Program> loadedProgram : loadedByName.values()) {
Program program = loadedProgram.getDomainObject();
Collection<Long> unresolvedExternalFunctionIds =
getUnresolvedExternalFunctionIds(program);
if (unresolvedExternalFunctionIds.size() == 0) { if (unresolvedExternalFunctionIds.size() == 0) {
return; return;
} }
@ -81,50 +84,23 @@ public class ELFExternalSymbolResolver {
continue; continue;
} }
DomainFile libDomainFile = projectData.getFile(libPath); Loaded<Program> loadedLib = loadedByName.get(libName);
if (libDomainFile == null) { if (loadedLib == null) {
messageLog.appendMsg("Referenced external program not found: " + libPath); messageLog.appendMsg("Referenced external program not found: " + libName);
continue; continue;
} }
Object consumer = new Object(); DomainObject libDomainObject = loadedLib.getDomainObject();
DomainObject libDomainObject = null;
try {
libDomainObject =
libDomainFile.getDomainObject(consumer, false, false, monitor);
if (!(libDomainObject instanceof Program)) {
messageLog.appendMsg(
"Referenced external program is not a program: " + libPath);
continue;
}
monitor.setMessage("Resolving symbols published by library " + libName); monitor.setMessage("Resolving symbols published by library " + libName);
resolveSymbolsToLibrary(program, unresolvedExternalFunctionIds, extLibrary, resolveSymbolsToLibrary(program, unresolvedExternalFunctionIds, extLibrary,
(Program) libDomainObject, messageLog, monitor); (Program) libDomainObject, messageLog, monitor);
} }
catch (IOException e) {
// failed to open library
messageLog.appendMsg("Failed to open library dependency project file: " +
libDomainFile.getPathname());
}
catch (VersionException e) {
messageLog.appendMsg(
"Referenced external program requires updgrade, unable to consider symbols: " +
libPath);
}
finally {
if (libDomainObject != null) {
libDomainObject.release(consumer);
}
}
}
messageLog.appendMsg("Unresolved external symbols which remain: " + messageLog.appendMsg("Unresolved external symbols which remain: " +
unresolvedExternalFunctionIds.size()); unresolvedExternalFunctionIds.size());
} }
finally { finally {
program.endTransaction(transactionID, true); program.endTransaction(transactionID, true);
} }
if (saveIfModified && program.canSave() && program.isChanged()) {
program.save("ExternalSymbolResolver", monitor);
} }
} }
@ -223,7 +199,7 @@ public class ELFExternalSymbolResolver {
return orderLibraryMap.values(); return orderLibraryMap.values();
} }
private static List<Library> getLibrarySearchList(Program program) { public static List<Library> getLibrarySearchList(Program program) {
List<Library> result = new ArrayList<>(); List<Library> result = new ArrayList<>();
ExternalManager externalManager = program.getExternalManager(); ExternalManager externalManager = program.getExternalManager();
for (String libName : getOrderedLibraryNamesNeeded(program)) { for (String libName : getOrderedLibraryNamesNeeded(program)) {

View file

@ -37,7 +37,7 @@ import ghidra.file.formats.android.xml.AndroidXmlFileSystem;
import ghidra.file.formats.zip.ZipFileSystem; import ghidra.file.formats.zip.ZipFileSystem;
import ghidra.formats.gfilesystem.FileSystemService; import ghidra.formats.gfilesystem.FileSystemService;
import ghidra.formats.gfilesystem.GFile; import ghidra.formats.gfilesystem.GFile;
import ghidra.framework.model.DomainFolder; import ghidra.framework.model.Project;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
import ghidra.util.exception.CancelledException; import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
@ -64,12 +64,13 @@ public class ApkLoader extends DexLoader {
} }
@Override @Override
protected List<LoadedProgram> loadProgram(ByteProvider provider, String programName, protected List<Loaded<Program>> loadProgram(ByteProvider provider, String programName,
DomainFolder programFolder, LoadSpec loadSpec, List<Option> options, MessageLog log, Project project, String programFolderPath, LoadSpec loadSpec, List<Option> options,
Object consumer, TaskMonitor monitor) throws CancelledException, IOException { MessageLog log, Object consumer, TaskMonitor monitor)
throws IOException, LoadException, CancelledException {
boolean success = false; boolean success = false;
List<LoadedProgram> allLoadedPrograms = new ArrayList<>(); List<Loaded<Program>> allLoadedPrograms = new ArrayList<>();
int dexIndex = 1;//DEX file numbering starts at 1 int dexIndex = 1;//DEX file numbering starts at 1
try (ZipFileSystem zipFS = openAPK(provider, monitor)) { try (ZipFileSystem zipFS = openAPK(provider, monitor)) {
while (!monitor.isCancelled()) { while (!monitor.isCancelled()) {
@ -86,9 +87,10 @@ public class ApkLoader extends DexLoader {
try (ByteProvider dexProvider = try (ByteProvider dexProvider =
zipFS.getByteProvider(classesDexFile, monitor)) { zipFS.getByteProvider(classesDexFile, monitor)) {
// defer to the super class (DexLoader) to actually load the DEX file // defer to the super class (DexLoader) to actually load the DEX file
List<LoadedProgram> loadedPrograms = List<Loaded<Program>> loadedPrograms =
super.loadProgram(dexProvider, classesDexFile.getName(), programFolder, super.loadProgram(dexProvider, classesDexFile.getName(), project,
loadSpec, options, log, consumer, monitor); concatenatePaths(programFolderPath, programName), loadSpec, options,
log, consumer, monitor);
allLoadedPrograms.addAll(loadedPrograms); allLoadedPrograms.addAll(loadedPrograms);
} }
@ -104,14 +106,16 @@ public class ApkLoader extends DexLoader {
release(allLoadedPrograms, consumer); release(allLoadedPrograms, consumer);
} }
} }
link(allLoadedPrograms.stream().map(e -> e.program()).toList(), log, monitor); if (allLoadedPrograms.isEmpty()) {
throw new LoadException("Operation finished with no programs to load");
}
link(allLoadedPrograms.stream().map(e -> e.getDomainObject()).toList(), log, monitor);
return allLoadedPrograms; return allLoadedPrograms;
} }
@Override @Override
protected boolean isOverrideMainProgramName() { public boolean loadsIntoNewFolder() {
//preserve the classesX.dex file names... return true;
return false;
} }
/** /**

View file

@ -198,7 +198,7 @@ public class MachoPrelinkFileSystem extends GFileSystemBase implements GFileSyst
throw e; throw e;
} }
finally { finally {
program.endTransaction(id, success); program.endTransaction(id, true); // More efficient to commit when program will be discarded
if (!success) { if (!success) {
program.release(consumer); program.release(consumer);
} }

View file

@ -17,7 +17,8 @@
//@category FunctionID //@category FunctionID
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.*; import java.util.ArrayList;
import java.util.HashSet;
import java.util.function.Predicate; import java.util.function.Predicate;
import ghidra.app.script.GhidraScript; import ghidra.app.script.GhidraScript;
@ -26,9 +27,9 @@ import ghidra.app.util.bin.format.coff.*;
import ghidra.app.util.bin.format.coff.archive.CoffArchiveHeader; import ghidra.app.util.bin.format.coff.archive.CoffArchiveHeader;
import ghidra.app.util.bin.format.coff.archive.CoffArchiveMemberHeader; import ghidra.app.util.bin.format.coff.archive.CoffArchiveMemberHeader;
import ghidra.app.util.importer.*; import ghidra.app.util.importer.*;
import ghidra.app.util.opinion.Loader; import ghidra.app.util.opinion.*;
import ghidra.app.util.opinion.MSCoffLoader;
import ghidra.framework.model.DomainFolder; import ghidra.framework.model.DomainFolder;
import ghidra.framework.model.DomainObject;
import ghidra.framework.store.local.LocalFileSystem; import ghidra.framework.store.local.LocalFileSystem;
import ghidra.program.model.lang.LanguageDescription; import ghidra.program.model.lang.LanguageDescription;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
@ -95,40 +96,43 @@ public class ImportMSLibs extends GhidraScript {
} }
offsetsSeen.add(archiveMemberHeader.getPayloadOffset()); offsetsSeen.add(archiveMemberHeader.getPayloadOffset());
if (archiveMemberHeader.isCOFF()) { if (archiveMemberHeader.isCOFF()) {
String preferredName = archiveMemberHeader.getName();
try (ByteProvider coffProvider = new ByteProviderWrapper(provider, try (ByteProvider coffProvider = new ByteProviderWrapper(provider,
archiveMemberHeader.getPayloadOffset(), archiveMemberHeader.getSize())) { archiveMemberHeader.getPayloadOffset(), archiveMemberHeader.getSize())) {
CoffFileHeader header = new CoffFileHeader(coffProvider); CoffFileHeader header = new CoffFileHeader(coffProvider);
if (CoffMachineType.isMachineTypeDefined(header.getMagic())) { if (CoffMachineType.isMachineTypeDefined(header.getMagic())) {
String preferredName = archiveMemberHeader.getName();
String[] splits = splitPreferredName(preferredName); String[] splits = splitPreferredName(preferredName);
List<Program> programs = LoadResults<? extends DomainObject> loadResults =
AutoImporter.importFresh( AutoImporter.importFresh(
coffProvider, coffProvider,
root, state.getProject(),
root.getPathname(),
this, this,
log, log,
new CancelOnlyWrappingTaskMonitor(monitor), new CancelOnlyWrappingTaskMonitor(monitor),
LOADER_FILTER, LOADER_FILTER,
LOADSPEC_CHOOSER, LOADSPEC_CHOOSER,
mangleNameBecauseDomainFoldersAreSoRetro(splits[splits.length - 1]), mangleNameBecauseDomainFoldersAreSoRetro(splits[splits.length - 1]),
OptionChooser.DEFAULT_OPTIONS, OptionChooser.DEFAULT_OPTIONS);
MultipleProgramsStrategy.ONE_PROGRAM_OR_EXCEPTION);
if (programs == null || programs.isEmpty()) { try {
printerr("no programs loaded from " + file + " - " + for (Loaded<? extends DomainObject> loaded : loadResults) {
preferredName); if (loaded.getDomainObject() instanceof Program program) {
} loaded.save(state.getProject(), log, monitor);
if (programs != null) {
for (Program program : programs) {
program.release(this);
DomainFolder destination = DomainFolder destination =
establishFolder(root, file, program, isDebug, splits); establishFolder(root, file, program, isDebug, splits);
program.getDomainFile().moveTo(destination); program.getDomainFile().moveTo(destination);
} }
} }
} }
finally {
loadResults.release(this);
}
}
}
catch (LoadException e) {
printerr("no programs loaded from " + file + " - " + preferredName);
} }
} }
} }

View file

@ -64,10 +64,8 @@ import ghidra.app.util.bin.format.coff.*;
import ghidra.app.util.bin.format.coff.archive.CoffArchiveHeader; import ghidra.app.util.bin.format.coff.archive.CoffArchiveHeader;
import ghidra.app.util.bin.format.coff.archive.CoffArchiveMemberHeader; import ghidra.app.util.bin.format.coff.archive.CoffArchiveMemberHeader;
import ghidra.app.util.importer.*; import ghidra.app.util.importer.*;
import ghidra.app.util.opinion.Loader; import ghidra.app.util.opinion.*;
import ghidra.app.util.opinion.MSCoffLoader; import ghidra.framework.model.*;
import ghidra.framework.model.DomainFile;
import ghidra.framework.model.DomainFolder;
import ghidra.framework.store.local.LocalFileSystem; import ghidra.framework.store.local.LocalFileSystem;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
import ghidra.util.InvalidNameException; import ghidra.util.InvalidNameException;
@ -198,24 +196,30 @@ public class MSLibBatchImportWorker extends GhidraScript {
Pair<DomainFolder, String> pair = Pair<DomainFolder, String> pair =
getFolderAndUniqueFile(currentLibraryFolder, preferredName); getFolderAndUniqueFile(currentLibraryFolder, preferredName);
LoadResults<? extends DomainObject> loadResults = null;
try {
loadResults = AutoImporter.importFresh(coffProvider,
state.getProject(), pair.first.getPathname(), this, log,
monitor, LOADER_FILTER, LOADSPEC_CHOOSER, pair.second,
OptionChooser.DEFAULT_OPTIONS);
List<Program> programs = AutoImporter.importFresh(coffProvider, for (Loaded<? extends DomainObject> loaded : loadResults) {
pair.first, this, log, monitor, LOADER_FILTER, LOADSPEC_CHOOSER, if (loaded.getDomainObject() instanceof Program program) {
pair.second, OptionChooser.DEFAULT_OPTIONS, loaded.save(state.getProject(), log, monitor);
MultipleProgramsStrategy.ONE_PROGRAM_OR_EXCEPTION); println(
"Imported " + program.getDomainFile().getPathname());
if (programs != null) {
for (Program program : programs) {
println("Imported " + program.getDomainFile().getPathname());
DomainFile progFile = program.getDomainFile(); DomainFile progFile = program.getDomainFile();
program.release(this);
if (!progFile.isVersioned()) { if (!progFile.isVersioned()) {
progFile.addToVersionControl(initalCheckInComment, false, progFile.addToVersionControl(initalCheckInComment,
monitor); false, monitor);
} }
}
}
}
finally {
if (loadResults != null) {
loadResults.release(this);
} }
} }
} }

View file

@ -15,7 +15,8 @@
*/ */
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.*; import java.util.ArrayList;
import java.util.HashSet;
import java.util.function.Predicate; import java.util.function.Predicate;
import generic.stl.Pair; import generic.stl.Pair;
@ -25,11 +26,10 @@ import ghidra.app.util.bin.format.coff.*;
import ghidra.app.util.bin.format.coff.archive.CoffArchiveHeader; import ghidra.app.util.bin.format.coff.archive.CoffArchiveHeader;
import ghidra.app.util.bin.format.coff.archive.CoffArchiveMemberHeader; import ghidra.app.util.bin.format.coff.archive.CoffArchiveMemberHeader;
import ghidra.app.util.importer.*; import ghidra.app.util.importer.*;
import ghidra.app.util.opinion.Loader; import ghidra.app.util.opinion.*;
import ghidra.app.util.opinion.MSCoffLoader;
import ghidra.framework.model.DomainFolder; import ghidra.framework.model.DomainFolder;
import ghidra.framework.model.DomainObject;
import ghidra.framework.store.local.LocalFileSystem; import ghidra.framework.store.local.LocalFileSystem;
import ghidra.program.model.listing.Program;
import ghidra.util.InvalidNameException; import ghidra.util.InvalidNameException;
import ghidra.util.exception.*; import ghidra.util.exception.*;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
@ -152,16 +152,18 @@ public class RecursiveRecursiveMSLibImport extends GhidraScript {
Pair<DomainFolder, String> pair = Pair<DomainFolder, String> pair =
establishProgramFolder(currentLibrary, preferredName); establishProgramFolder(currentLibrary, preferredName);
List<Program> programs = AutoImporter.importFresh(coffProvider, LoadResults<? extends DomainObject> loadResults =
pair.first, this, log, monitor, LOADER_FILTER, LOADSPEC_CHOOSER, AutoImporter.importFresh(coffProvider, state.getProject(),
pair.first.getPathname(), this, log, monitor, LOADER_FILTER,
LOADSPEC_CHOOSER,
mangleNameBecauseDomainFoldersAreSoRetro(pair.second), mangleNameBecauseDomainFoldersAreSoRetro(pair.second),
OptionChooser.DEFAULT_OPTIONS, OptionChooser.DEFAULT_OPTIONS);
MultipleProgramsStrategy.ONE_PROGRAM_OR_EXCEPTION);
if (programs != null) { try {
for (Program program : programs) { loadResults.save(state.getProject(), this, log, monitor);
program.release(this);
} }
finally {
loadResults.release(this);
} }
} }
} }

View file

@ -19,7 +19,6 @@ import java.awt.*;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.List;
import javax.swing.*; import javax.swing.*;
@ -30,8 +29,10 @@ import generic.theme.GThemeDefaults.Colors.Palette;
import ghidra.app.plugin.core.codebrowser.CodeViewerProvider; import ghidra.app.plugin.core.codebrowser.CodeViewerProvider;
import ghidra.app.plugin.core.references.*; import ghidra.app.plugin.core.references.*;
import ghidra.app.util.importer.*; import ghidra.app.util.importer.*;
import ghidra.app.util.opinion.LoadResults;
import ghidra.app.util.opinion.LoaderService; import ghidra.app.util.opinion.LoaderService;
import ghidra.framework.main.DataTreeDialog; import ghidra.framework.main.DataTreeDialog;
import ghidra.framework.model.Project;
import ghidra.program.model.listing.CodeUnit; import ghidra.program.model.listing.CodeUnit;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.Memory; import ghidra.program.model.mem.Memory;
@ -290,16 +291,18 @@ public class ReferencesPluginScreenShots extends GhidraScreenShotGenerator {
private void importFile(File file) throws CancelledException, DuplicateNameException, private void importFile(File file) throws CancelledException, DuplicateNameException,
InvalidNameException, VersionException, IOException { InvalidNameException, VersionException, IOException {
String programNameOverride = null; String programNameOverride = null;
List<Program> programs = AutoImporter.importFresh(file, null, this, new MessageLog(), Project project = env.getProject();
LoadResults<Program> loadResults = AutoImporter.importFresh(file, project,
project.getProjectData().getRootFolder().getPathname(), this, new MessageLog(),
TaskMonitor.DUMMY, LoaderService.ACCEPT_ALL, LoadSpecChooser.CHOOSE_THE_FIRST_PREFERRED, TaskMonitor.DUMMY, LoaderService.ACCEPT_ALL, LoadSpecChooser.CHOOSE_THE_FIRST_PREFERRED,
programNameOverride, OptionChooser.DEFAULT_OPTIONS, programNameOverride, OptionChooser.DEFAULT_OPTIONS);
MultipleProgramsStrategy.ALL_PROGRAMS);
Program p = programs.get(0); try {
env.getProject() loadResults.getPrimary().save(project, new MessageLog(), TaskMonitor.DUMMY);
.getProjectData() }
.getRootFolder() finally {
.createFile(p.getName(), p, loadResults.release(this);
TaskMonitor.DUMMY); }
} }
} }