Merge remote-tracking branch

'origin/GP-4919_ryanmkurtz_loadlibs--SQUASHED' (Closes #396)
This commit is contained in:
Ryan Kurtz 2024-09-26 12:30:19 -04:00
commit b78072c76f
10 changed files with 418 additions and 52 deletions

View File

@ -184,6 +184,32 @@
in it.</LI>
</UL>
</BLOCKQUOTE>
<H3><A name="Load_Libraries"></A>Load Libraries</H3>
<BLOCKQUOTE>
<P>This action is used to load libraries into the project and link them to an existing
program. The program must be open in the tool to perform this action.</P>
<P><B>NOTE: </B>If you know at the time of import that you want to load/link libraries, it
is preferred to set the library loading options directly from the <A href="#Importer_Dialog">
Importer Dialog's</A> <A href="#Common_Options">Options...</A> button.
<H4>Steps:</H4>
<UL>
<LI>Invoke the action from the <B>File<IMG src="help/shared/arrow.gif" alt="">Load
Libraries...</B> menu item.</LI>
<LI>Use the options dialog that appears to control the library import settings.</LI>
<LI>Press OK on the dialog to initiate importing any discovered libraries.</LI>
<LI>When complete, the currently open program should have additional
<A href="help/topics/ReferencesPlugin/external_program_names.htm#ExternalNamesDialog">
External Programs</A> linked if matching libraries were found.</LI>
</UL>
</BLOCKQUOTE>
</BLOCKQUOTE>
<H2>Other Import Actions</H2>

View File

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -36,6 +36,7 @@ public class Option {
private final Class<?> valueClass;
private final String commandLineArgument;
private final String stateKey;
private final boolean hidden;
private Object value;
private OptionListener listener;
@ -93,7 +94,7 @@ public class Option {
* @param group Name for group of options
*/
public Option(String name, Class<?> valueClass, Object value, String arg, String group) {
this(name, valueClass, value, arg, group, null);
this(name, valueClass, value, arg, group, null, false);
}
/**
@ -105,15 +106,17 @@ public class Option {
* @param arg the option's command line argument
* @param group Name for group of options
* @param stateKey state key name
* @param hidden true if this option should be hidden from the user; otherwise, false
*/
public Option(String name, Class<?> valueClass, Object value, String arg, String group,
String stateKey) {
String stateKey, boolean hidden) {
this.name = name;
this.valueClass = valueClass;
this.commandLineArgument = arg;
this.group = group;
this.value = value;
this.stateKey = stateKey;
this.hidden = hidden;
}
public void setOptionListener(OptionListener listener) {
@ -283,6 +286,13 @@ public class Option {
return state;
}
/**
* {@return whether or not this option is hidden}
*/
public boolean isHidden() {
return hidden;
}
@Override
public String toString() {
return ToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE);
@ -293,7 +303,7 @@ public class Option {
* @return a copy of this Option object.
*/
public Option copy() {
return new Option(name, valueClass, value, commandLineArgument, group, stateKey);
return new Option(name, valueClass, value, commandLineArgument, group, stateKey, hidden);
}
private static Class<?> getValueClass(Object v) {

View File

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -55,7 +55,7 @@ public class OptionsEditorPanel extends JPanel {
super(new VerticalLayout(5));
this.addressFactoryService = addressFactoryService;
setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
columns = options.size() > MAX_PER_COLUMN ? 2 : 1;
columns = options.stream().filter(o -> !o.isHidden()).count() > MAX_PER_COLUMN ? 2 : 1;
Map<String, List<Option>> optionGroupMap = organizeByGroup(options);
for (List<Option> optionGroup : optionGroupMap.values()) {
@ -156,6 +156,9 @@ public class OptionsEditorPanel extends JPanel {
LazyMap.lazyMap(new LinkedHashMap<>(), () -> new ArrayList<>());
for (Option option : options) {
if (option.isHidden()) {
continue;
}
String group = option.getGroup();
List<Option> optionGroup = map.get(group);
optionGroup.add(option);

View File

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -42,7 +42,7 @@ public class DomainFolderOption extends Option {
* @param arg The option's command line argument (could be null)
*/
public DomainFolderOption(String name, String arg) {
super(name, String.class, "", arg, null, Loader.OPTIONS_PROJECT_SAVE_STATE_KEY);
super(name, String.class, "", arg, null, Loader.OPTIONS_PROJECT_SAVE_STATE_KEY, false);
}
@Override

View File

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -27,6 +27,7 @@ import ghidra.framework.Platform;
import ghidra.framework.main.AppInfo;
import ghidra.framework.model.Project;
import ghidra.framework.options.SaveState;
import ghidra.program.model.listing.Program;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
@ -53,13 +54,14 @@ public class LibrarySearchPathManager {
* Returns a {@link List} of {@link FSRL}s to search for libraries
*
* @param provider The {@link ByteProvider} of the program being loaded
* @param program The {@link Program} being loaded
* @param log The log
* @param monitor A cancellable monitor
* @return a {@link List} of {@link FSRL}s to search for libraries
* @throws CancelledException if the user cancelled the operation
*/
public static synchronized List<FSRL> getLibraryFsrlList(ByteProvider provider, MessageLog log,
TaskMonitor monitor) throws CancelledException {
public static synchronized List<FSRL> getLibraryFsrlList(ByteProvider provider, Program program,
MessageLog log, TaskMonitor monitor) throws CancelledException {
FileSystemService fsService = FileSystemService.getInstance();
List<FSRL> fsrlList = new ArrayList<>();
for (String path : pathSet) {
@ -69,11 +71,18 @@ public class LibrarySearchPathManager {
try {
if (path.equals(".")) {
FSRL providerFsrl = provider.getFSRL();
if (providerFsrl == null) {
providerFsrl = FSRL.fromProgram(program);
}
if (providerFsrl != null) {
try (RefdFile fileRef = fsService.getRefdFile(providerFsrl, monitor)) {
GFile parentFile = fileRef.file.getParentFile();
fsrl = parentFile.getFSRL();
}
catch (IOException e) {
log.appendMsg("Skipping '.' search path: ", e.getMessage());
continue;
}
}
}
else {
@ -91,9 +100,6 @@ public class LibrarySearchPathManager {
log.appendException(e2);
}
}
catch (IOException e) {
log.appendException(e);
}
if (fsrl != null) {
fsrlList.add(fsrl);
}

View File

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -29,6 +29,7 @@ import org.apache.commons.lang3.ObjectUtils;
import ghidra.app.util.Option;
import ghidra.app.util.OptionUtils;
import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.bin.FileBytesProvider;
import ghidra.app.util.importer.*;
import ghidra.formats.gfilesystem.*;
import ghidra.framework.model.*;
@ -67,6 +68,9 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
public static final String LIBRARY_DEST_FOLDER_OPTION_NAME = "Library Destination Folder";
static final String LIBRARY_DEST_FOLDER_OPTION_DEFAULT = "";
public static final String LOAD_ONLY_LIBRARIES_OPTION_NAME = "Only Load Libraries"; // hidden
static final boolean LOAD_ONLY_LIBRARIES_OPTION_DEFAULT = false;
/**
* Loads bytes in a particular format into the given {@link Program}.
*
@ -94,11 +98,31 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
boolean success = false;
try {
// Load the primary program
Program program = doLoad(provider, loadedName, loadSpec, libraryNameList, options,
consumer, log, monitor);
loadedProgramList.add(new Loaded<>(program, loadedName, projectFolderPath));
log.appendMsg("------------------------------------------------\n");
// Load (or get) the primary program
Program program = null;
if (!shouldLoadOnlyLibraries(options)) {
if (provider instanceof FileBytesProvider) {
throw new LoadException("Cannot load an already loaded program");
}
program = doLoad(provider, loadedName, loadSpec, libraryNameList, options, consumer,
log, monitor);
loadedProgramList.add(new Loaded<>(program, loadedName, projectFolderPath));
log.appendMsg("------------------------------------------------\n");
}
else if (project != null) {
ProjectData projectData = project.getProjectData();
DomainFile domainFile = projectData.getFile(projectFolderPath + "/" + loadedName);
if (domainFile == null) {
throw new LoadException(
"Cannot load only libraries for a non-existant program");
}
program = (Program) domainFile.getOpenedDomainObject(consumer);
if (program == null) {
throw new LoadException("Failed to acquire a Program");
}
loadedProgramList.add(new Loaded<>(program, domainFile));
libraryNameList.addAll(getLibraryNames(provider, program));
}
// Load the libraries
List<Loaded<Program>> libraries = loadLibraries(provider, program, project,
@ -186,6 +210,9 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
Loader.COMMAND_LINE_ARG_PREFIX + "-libraryLoadDepth"));
list.add(new DomainFolderOption(LIBRARY_DEST_FOLDER_OPTION_NAME,
Loader.COMMAND_LINE_ARG_PREFIX + "-libraryDestinationFolder"));
list.add(new Option(LOAD_ONLY_LIBRARIES_OPTION_NAME, Boolean.class,
LOAD_ONLY_LIBRARIES_OPTION_DEFAULT,
Loader.COMMAND_LINE_ARG_PREFIX + "-loadOnlyLibraries", null, null, true));
return list;
}
@ -197,7 +224,8 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
for (Option option : options) {
String name = option.getName();
if (name.equals(LINK_EXISTING_OPTION_NAME) ||
name.equals(LOAD_LIBRARY_OPTION_NAME)) {
name.equals(LOAD_LIBRARY_OPTION_NAME) ||
name.equals(LOAD_ONLY_LIBRARIES_OPTION_NAME)) {
if (!Boolean.class.isAssignableFrom(option.getValueClass())) {
return "Invalid type for option: " + name + " - " + option.getValueClass();
}
@ -278,6 +306,17 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
LOAD_LIBRARY_OPTION_DEFAULT);
}
/**
* Checks to see if only libraries should be loaded (i.e., not the main program)
*
* @param options a {@link List} of {@link Option}s
* @return True if only libraries should be loaded; otherwise, false
*/
protected boolean shouldLoadOnlyLibraries(List<Option> options) {
return OptionUtils.getOption(LOAD_ONLY_LIBRARIES_OPTION_NAME, options,
LOAD_ONLY_LIBRARIES_OPTION_DEFAULT);
}
/**
* Gets the desired recursive library load depth
*
@ -442,7 +481,7 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
List<FileSystemSearchPath> customSearchPaths =
getCustomLibrarySearchPaths(provider, options, log, monitor);
List<FileSystemSearchPath> searchPaths =
getLibrarySearchPaths(provider, options, log, monitor);
getLibrarySearchPaths(provider, program, options, log, monitor);
DomainFolder linkSearchFolder = getLinkSearchFolder(project, projectFolderPath, options);
String libraryDestFolderPath =
getLibraryDestinationFolderPath(project, projectFolderPath, options);
@ -823,23 +862,8 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
try {
log.appendMsg("Loading %s...".formatted(provider.getFSRL()));
load(provider, loadSpec, options, program, monitor, log);
createDefaultMemoryBlocks(program, language, log);
ExternalManager extMgr = program.getExternalManager();
String[] externalNames = extMgr.getExternalLibraryNames();
Comparator<String> comparator = getLibraryNameComparator();
Arrays.sort(externalNames, comparator);
for (String name : externalNames) {
if (comparator.compare(name, provider.getName()) == 0 ||
comparator.compare(name, program.getName()) == 0 ||
Library.UNKNOWN.equals(name)) {
// skip self-references and UNKNOWN library...
continue;
}
libraryNameList.add(name);
}
libraryNameList.addAll(getLibraryNames(provider, program));
success = true;
return program;
}
@ -851,6 +875,32 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
}
}
/**
* Gets a {@link List} of library names that the given {@link Program} imports from
*
* @param provider The {@link ByteProvider} to get the library names from
* @param program The {@link Program} to get the library names from
* @return A {@link List} of library names that the given {@link Program} imports from
*
*/
private List<String> getLibraryNames(ByteProvider provider, Program program) {
List<String> libraryNames = new ArrayList<>();
ExternalManager extMgr = program.getExternalManager();
String[] externalNames = extMgr.getExternalLibraryNames();
Comparator<String> comparator = getLibraryNameComparator();
Arrays.sort(externalNames, comparator);
for (String name : externalNames) {
if (comparator.compare(name, provider.getName()) == 0 ||
comparator.compare(name, program.getName()) == 0 ||
Library.UNKNOWN.equals(name)) {
// skip self-references and UNKNOWN library...
continue;
}
libraryNames.add(name);
}
return libraryNames;
}
/**
* For each {@link Loaded} {@link Program} in the given list, fix up its external library
* entries so that they point to a path in the project.
@ -1011,6 +1061,7 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
* libraries
*
* @param provider The {@link ByteProvider} of the program being loaded
* @param program The {@link Program} being loaded
* @param options The options
* @param log The log
* @param monitor A cancelable task monitor
@ -1018,7 +1069,7 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
* libraries
* @throws CancelledException if the user cancelled the load
*/
private List<FileSystemSearchPath> getLibrarySearchPaths(ByteProvider provider,
private List<FileSystemSearchPath> getLibrarySearchPaths(ByteProvider provider, Program program,
List<Option> options, MessageLog log, TaskMonitor monitor) throws CancelledException {
if (!isLoadLibraries(options) && !shouldSearchAllPaths(options)) {
return List.of();
@ -1028,7 +1079,8 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
List<FileSystemSearchPath> result = new ArrayList<>();
boolean success = false;
try {
for (FSRL fsrl : LibrarySearchPathManager.getLibraryFsrlList(provider, log, monitor)) {
for (FSRL fsrl : LibrarySearchPathManager.getLibraryFsrlList(provider, program, log,
monitor)) {
if (fsService.isLocal(fsrl)) {
try {
FileSystemRef fileRef =

View File

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -38,6 +38,7 @@ public class Loaded<T extends DomainObject> {
private String projectFolderPath;
private DomainFile domainFile;
private boolean ignoreSave;
/**
* Creates a new {@link Loaded} object
@ -56,6 +57,19 @@ public class Loaded<T extends DomainObject> {
setProjectFolderPath(projectFolderPath);
}
/**
* Creates a {@link Loaded} view on an existing {@link DomainFile}. This type of {@link Loaded}
* object cannot be saved.
*
* @param domainObject The loaded {@link DomainObject}
* @param domainFile The {@link DomainFile} to be loaded
*/
public Loaded(T domainObject, DomainFile domainFile) {
this(domainObject, domainFile.getName(), domainFile.getParent().getPathname());
this.domainFile = domainFile;
this.ignoreSave = true;
}
/**
* Gets the loaded {@link DomainObject}
*
@ -140,6 +154,10 @@ public class Loaded<T extends DomainObject> {
public DomainFile save(Project project, MessageLog messageLog, TaskMonitor monitor)
throws CancelledException, ClosedException, IOException {
if (ignoreSave) {
return domainFile;
}
if (domainObject.isClosed()) {
throw new ClosedException(
"Cannot saved closed DomainObject: " + domainObject.getName());
@ -152,7 +170,7 @@ public class Loaded<T extends DomainObject> {
}
catch (FileNotFoundException e) {
// DomainFile was already saved, but no longer exists.
// Allow the save to proceeded.
// Allow the save to proceed.
domainFile = null;
}

View File

@ -35,8 +35,7 @@ import ghidra.app.plugin.PluginCategoryNames;
import ghidra.app.services.FileImporterService;
import ghidra.app.services.ProgramManager;
import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.opinion.LoaderMap;
import ghidra.app.util.opinion.LoaderService;
import ghidra.app.util.opinion.*;
import ghidra.formats.gfilesystem.FSRL;
import ghidra.formats.gfilesystem.FileCache.FileCacheEntry;
import ghidra.formats.gfilesystem.FileCache.FileCacheEntryBuilder;
@ -89,6 +88,7 @@ public class ImporterPlugin extends Plugin
private DockingAction importAction;
private DockingAction importSelectionAction;
private DockingAction addToProgramAction;
private DockingAction loadLibrariesAction;
private GhidraFileChooser chooser;
private FrontEndService frontEndService;
private DockingAction batchImportAction;
@ -116,6 +116,7 @@ public class ImporterPlugin extends Plugin
setupImportAction();
setupImportSelectionAction();
setupAddToProgramAction();
setupLoadLibrariesAction();
setupBatchImportAction();
}
@ -150,9 +151,29 @@ public class ImporterPlugin extends Plugin
Program currentProgram = pape.getActiveProgram();
importSelectionAction.setEnabled(currentProgram != null);
addToProgramAction.setEnabled(currentProgram != null);
loadLibrariesAction.setEnabled(shouldEnableLoadLibraries(currentProgram));
}
}
private boolean shouldEnableLoadLibraries(Program program) {
if (program == null) {
return false;
}
ByteProvider provider = ImporterUtilities.getProvider(program);
if (provider == null) {
return false;
}
LoadSpec loadSpec = ImporterUtilities.getLoadSpec(provider, program);
if (loadSpec == null) {
return false;
}
return loadSpec.getLoader()
.getDefaultOptions(provider, loadSpec, null, false)
.stream()
.anyMatch(e -> e.getName()
.equals(AbstractLibrarySupportLoader.LOAD_ONLY_LIBRARIES_OPTION_NAME));
}
@Override
public void importFiles(DomainFolder destFolder, List<File> files) {
@ -398,6 +419,27 @@ public class ImporterPlugin extends Plugin
}
}
private void setupLoadLibrariesAction() {
String title = "Load Libraries";
loadLibrariesAction = new DockingAction(title, this.getName()) {
@Override
public void actionPerformed(ActionContext context) {
doLoadLibraries();
}
};
loadLibrariesAction.setMenuBarData(new MenuData(new String[] { "&File", title + "..." },
null, IMPORT_MENU_GROUP, MenuData.NO_MNEMONIC, "zzz"));
loadLibrariesAction.setDescription(IMPORTER_PLUGIN_DESC);
loadLibrariesAction.setEnabled(false);
// Load libraries makes no sense in the front end tool, but we create it so that the
// loadLibrariesAction won't be null and we would have to check that in other places.
if (!(tool instanceof FrontEndTool)) {
tool.addAction(loadLibrariesAction);
}
}
private static DomainFolder getFolderFromContext(ActionContext context) {
Object contextObj = context.getContextObject();
if (contextObj instanceof DomainFolderNode) {
@ -478,6 +520,15 @@ public class ImporterPlugin extends Plugin
}
private void doLoadLibraries() {
ProgramManager manager = tool.getService(ProgramManager.class);
Program program = manager.getCurrentProgram();
TaskLauncher.launchModal("Show Load Libraries Dialog", monitor -> {
ImporterUtilities.showLoadLibrariesDialog(program, tool, manager, monitor);
});
}
protected void doImportSelectionAction(Program program, ProgramSelection selection) {
if (selection == null || selection.getNumAddressRanges() != 1) {
return;

View File

@ -24,9 +24,9 @@ import docking.widgets.OptionDialog;
import ghidra.app.plugin.core.help.AboutDomainObjectUtils;
import ghidra.app.services.FileSystemBrowserService;
import ghidra.app.services.ProgramManager;
import ghidra.app.util.GenericHelpTopics;
import ghidra.app.util.Option;
import ghidra.app.util.*;
import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.bin.FileBytesProvider;
import ghidra.app.util.importer.MessageLog;
import ghidra.app.util.opinion.*;
import ghidra.formats.gfilesystem.*;
@ -35,7 +35,10 @@ import ghidra.framework.main.FrontEndTool;
import ghidra.framework.model.*;
import ghidra.framework.plugintool.PluginTool;
import ghidra.plugins.importer.batch.BatchImportDialog;
import ghidra.program.database.mem.FileBytes;
import ghidra.program.model.address.AddressFactory;
import ghidra.program.model.lang.LanguageCompilerSpecPair;
import ghidra.program.model.lang.LanguageNotFoundException;
import ghidra.program.model.listing.Program;
import ghidra.program.util.DefaultLanguageService;
import ghidra.util.*;
@ -265,6 +268,34 @@ public class ImporterUtilities {
}
public static void showLoadLibrariesDialog(Program program, PluginTool tool,
ProgramManager manager, TaskMonitor monitor) {
Objects.requireNonNull(monitor);
// Don't allow Load Libraries while "things are happening" to the program
if (!program.canLock()) {
Msg.showWarn(null, null, LoadLibrariesOptionsDialog.TITLE,
"Cannot Load Libraries while program is locked. Please wait or stop running tasks.");
return;
}
try {
ByteProvider provider = getProvider(program);
LoadSpec loadSpec = getLoadSpec(provider, program);
AddressFactory addressFactory =
loadSpec.getLanguageCompilerSpec().getLanguage().getAddressFactory();
SystemUtilities.runSwingLater(() -> {
OptionsDialog dialog = new LoadLibrariesOptionsDialog(provider, program, tool,
loadSpec, () -> addressFactory);
tool.showDialog(dialog);
});
}
catch (LanguageNotFoundException e) {
Msg.showError(null, null, LoadLibrariesOptionsDialog.TITLE, "Language not found.", e);
}
}
/**
* Constructs a {@link ImporterDialog} and shows it in the swing thread.
*
@ -493,4 +524,43 @@ public class ImporterUtilities {
AboutDomainObjectUtils.displayInformation(tool, domainFile, metadata,
"Import Results Summary", info, helpLocation);
}
/**
* Gets a {@link ByteProvider} based on the {@link FileBytes} of the given {@link Program}.
* <p>
* NOTE: If the {@link Program} has more than one {@link FileBytes} associated with it, the
* first one is used (this is typically the bytes of the originally imported file).
*
* @param program The {@link Program}
* @return A {@link ByteProvider} based on the {@link FileBytes} of the given {@link Program},
* or null if the {@link Program} doesn't have an associated {@link FileBytes}
*/
static ByteProvider getProvider(Program program) {
List<FileBytes> allFileBytes = program.getMemory().getAllFileBytes();
return !allFileBytes.isEmpty() ? new FileBytesProvider(allFileBytes.get(0)) : null;
}
/**
* Get's the {@link LoadSpec} that was used to import the given {@link Program}
*
* @param provider The original bytes of the {@link Program}
* @param program The {@link Program}
* @return The {@link LoadSpec} that was used to import the given {@link Program}, or null if
* it could not be determined
*/
static LoadSpec getLoadSpec(ByteProvider provider, Program program) {
LoaderMap loaderMap = LoaderService.getSupportedLoadSpecs(provider,
loader -> loader.getName().equalsIgnoreCase(program.getExecutableFormat()));
Loader loader = loaderMap.firstKey();
if (loader == null) {
return null;
}
return loaderMap.get(loader)
.stream()
.filter(
e -> e.getLanguageCompilerSpec().equals(program.getLanguageCompilerSpecPair()))
.findFirst()
.orElse(null);
}
}

View File

@ -0,0 +1,130 @@
/* ###
* 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.plugin.importer;
import static ghidra.app.util.opinion.AbstractLibrarySupportLoader.*;
import java.util.ArrayList;
import java.util.List;
import docking.widgets.dialogs.MultiLineMessageDialog;
import ghidra.app.util.*;
import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.importer.MessageLog;
import ghidra.app.util.opinion.*;
import ghidra.framework.model.DomainObject;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.listing.Program;
import ghidra.util.Msg;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskLauncher;
/**
* Dialog for editing the options for the "Load Libraries" action
*/
public class LoadLibrariesOptionsDialog extends OptionsDialog {
public static final String TITLE = "Load Libraries";
private ByteProvider provider;
private Program program;
private PluginTool tool;
private LoadSpec loadSpec;
/**
* Creates a new {@link LoadLibrariesOptionsDialog}
*
* @param provider The {@link Program}'s bytes
* @param program The {@link Program} to load libraries into
* @param tool The tool
* @param loadSpec The {@link LoadSpec} that was used to load the {@link Program}
* @param addressFactoryService The {@link AddressFactoryService} to use
*/
public LoadLibrariesOptionsDialog(ByteProvider provider, Program program, PluginTool tool,
LoadSpec loadSpec, AddressFactoryService addressFactoryService) {
super(getLoadLibraryOptions(provider, loadSpec), optionList -> loadSpec.getLoader()
.validateOptions(provider, loadSpec, optionList, null),
addressFactoryService);
setTitle(TITLE);
this.provider = provider;
this.program = program;
this.tool = tool;
this.loadSpec = loadSpec;
}
@Override
protected void okCallback() {
TaskLauncher.launchNonModal(TITLE, monitor -> {
super.okCallback();
try {
Object consumer = new Object();
MessageLog messageLog = new MessageLog();
LoadResults<? extends DomainObject> loadResults = loadSpec.getLoader()
.load(provider, program.getDomainFile().getName(), tool.getProject(),
program.getDomainFile().getParent().getPathname(), loadSpec,
getOptions(), messageLog, consumer, monitor);
loadResults.save(tool.getProject(), consumer, messageLog, monitor);
// Display results
String importMessages = messageLog.toString();
if (!Loader.loggingDisabled && !importMessages.isEmpty()) {
Msg.info(ImporterUtilities.class, TITLE + ":\n" + importMessages);
}
MultiLineMessageDialog.showModalMessageDialog(null, TITLE, "Results",
importMessages, MultiLineMessageDialog.INFORMATION_MESSAGE);
loadResults.release(consumer);
}
catch (CancelledException e) {
// no need to show a message
}
catch (Exception e) {
Msg.showError(LoadLibrariesOptionsDialog.class, tool.getActiveWindow(), TITLE,
"Error loading libraries for: " + program.getName(), e);
}
});
}
/**
* Gets a {@link List} of {@link Option}s that relate to loading libraries
*
* @param provider The {@link ByteProvider} of the program
* @param loadSpec The {@link LoadSpec} that was used to load the program
* @return A {@link List} of {@link Option}s that relate to loading libraries
* @see AbstractLibrarySupportLoader
*/
private static List<Option> getLoadLibraryOptions(ByteProvider provider, LoadSpec loadSpec) {
List<Option> options = new ArrayList<>();
for (Option option : loadSpec.getLoader()
.getDefaultOptions(provider, loadSpec, null, false)) {
switch (option.getName()) {
case LOAD_ONLY_LIBRARIES_OPTION_NAME:
case LOAD_LIBRARY_OPTION_NAME:
option.setValue(true);
case LINK_EXISTING_OPTION_NAME:
case LINK_SEARCH_FOLDER_OPTION_NAME:
case LIBRARY_SEARCH_PATH_DUMMY_OPTION_NAME:
case LIBRARY_DEST_FOLDER_OPTION_NAME:
options.add(option);
break;
default:
break;
}
}
return options;
}
}