mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2024-11-12 23:23:17 +00:00
GP-4919: New 'Load Libraries' action that lets you load libraries after import
This commit is contained in:
parent
493d87cee0
commit
c5ebbe5c8d
@ -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>
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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 =
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user