diff --git a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/ScriptPathsPropertyEditor.java b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/ScriptPathsPropertyEditor.java
index fa2a3c21fc..8b627f4d92 100644
--- a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/ScriptPathsPropertyEditor.java
+++ b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/ScriptPathsPropertyEditor.java
@@ -89,7 +89,7 @@ public class ScriptPathsPropertyEditor extends AbstractTypedPropertyEditor
[-commit ["<comment>"]]
[-max-cpu <max cpu cores to use>]
+
[-librarySearchPaths <path1>[;<path2>...]]
[-loader <desired loader name>]
The project folder that will get searched for existing library programs. If left empty, the folder that the main program is being imported to will be searched.
- ---Searches the executable's directory to recursively resolve the external libraries used - by the executable. The entire library dependency tree will be traversed in a depth-first - manner and a program will be created for each found library (if it doesn't exist already). - The external references - in these programs will be resolved.
-
Searches a user-defined path list to recursively resolve the external libraries used @@ -696,8 +686,12 @@
Library Search Path
-@@ -734,7 +728,8 @@The Library Search Path dialog is used to specify the directories that Ghidra should use - to resolve external libraries (e.g.; *.dll, *.so) while importing.
+The Library Search Path dialog is used to specify the directories, container files, + and/or FSRLs that Ghidra should use to resolve external libraries (e.g.; *.dll, *.so) while + importing. A "." can be added to specify the the program's import location. FSRLs can be + added via the + File System Browser context menu. +
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/Option.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/Option.java index e8e8920e0a..e67fa175d8 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/Option.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/Option.java @@ -17,6 +17,13 @@ package ghidra.app.util; import java.awt.Component; +import org.apache.commons.lang3.BooleanUtils; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +import ghidra.framework.main.AppInfo; +import ghidra.framework.model.Project; +import ghidra.framework.options.SaveState; import ghidra.program.model.address.*; import ghidra.util.NumericUtilities; @@ -28,6 +35,7 @@ public class Option { private final String name; private final Class> valueClass; private final String commandLineArgument; + private final String stateKey; private Object value; private OptionListener listener; @@ -85,11 +93,27 @@ 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); + } + + /** + * Construct a new Option + * + * @param name name of the option + * @param valueClass class of the option's value + * @param value value of the option + * @param arg the option's command line argument + * @param group Name for group of options + * @param stateKey state key name + */ + public Option(String name, Class> valueClass, Object value, String arg, String group, + String stateKey) { this.name = name; this.valueClass = valueClass; this.commandLineArgument = arg; this.group = group; this.value = value; + this.stateKey = stateKey; } public void setOptionListener(OptionListener listener) { @@ -110,29 +134,28 @@ public class Option { } /** - * Return the class of the value for this Option. + * {@return the class of the value for this option} */ public Class> getValueClass() { return valueClass; } /** - * Return the group name for this option; may be null if group was - * not specified. + * {@return the group name for this option; may be null if group was not specified} */ public String getGroup() { return group; } /** - * Return the name of this Option. + * {@return the name of this option} */ public String getName() { return name; } /** - * Return the value of this Option. + * {@return the value of this option} */ public Object getValue() { return value; @@ -168,19 +191,12 @@ public class Option { return false; } if (Boolean.class.isAssignableFrom(getValueClass())) { - Boolean b = null; - if (str.equalsIgnoreCase("true") || str.equalsIgnoreCase("t") || - str.equalsIgnoreCase("yes") || str.equalsIgnoreCase("y")) { - b = true; + try { + setValue(BooleanUtils.toBoolean(str, "true", "false")); } - else if (str.equalsIgnoreCase("false") || str.equalsIgnoreCase("f") || - str.equalsIgnoreCase("no") || str.equalsIgnoreCase("n")) { - b = false; - } - if (b == null) { + catch (IllegalArgumentException e) { return false; } - setValue(b); } else if (HexLong.class.isAssignableFrom(getValueClass())) { try { @@ -231,18 +247,45 @@ public class Option { } /** - * Return the command line argument for this Option. - * - * @return The command line argument for this Option. Could be null. + * {@return the command line argument for this option (could be null)} */ public String getArg() { return commandLineArgument; } + /** + * {@return the state key name (could be null)} + */ + public String getStateKey() { + return stateKey; + } + + /** + * {@return the current project state associated with this option (could be null)} + */ + public SaveState getState() { + Project project = AppInfo.getActiveProject(); + if (project == null) { + return null; + } + final SaveState state; + SaveState existingState = stateKey != null ? project.getSaveableData(stateKey) : null; + if (existingState != null) { + state = existingState; + } + else if (stateKey != null) { + state = new SaveState(); + project.setSaveableData(stateKey, state); + } + else { + state = null; + } + return state; + } + @Override public String toString() { - return "Group:\"" + group + "\" Name:\"" + name + "\" Arg:\"" + commandLineArgument + - "\" Type:\"" + valueClass + "\" Value:\"" + value + "\""; + return ToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE); } /** @@ -250,7 +293,7 @@ public class Option { * @return a copy of this Option object. */ public Option copy() { - return new Option(name, valueClass, value, commandLineArgument, group); + return new Option(name, valueClass, value, commandLineArgument, group, stateKey); } private static Class> getValueClass(Object v) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/OptionsEditorPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/OptionsEditorPanel.java index cb01d55978..37baa71793 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/OptionsEditorPanel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/OptionsEditorPanel.java @@ -15,8 +15,6 @@ */ package ghidra.app.util; -import static ghidra.framework.main.DataTreeDialogType.*; - import java.awt.*; import java.util.*; import java.util.List; @@ -28,17 +26,10 @@ import javax.swing.event.DocumentListener; import org.apache.commons.collections4.map.LazyMap; -import docking.DockingWindowManager; -import docking.widgets.button.BrowseButton; import docking.widgets.checkbox.GCheckBox; import docking.widgets.combobox.GComboBox; import docking.widgets.label.GLabel; import docking.widgets.textfield.IntegerTextField; -import ghidra.app.util.opinion.*; -import ghidra.framework.main.AppInfo; -import ghidra.framework.main.DataTreeDialog; -import ghidra.framework.model.DomainFolder; -import ghidra.framework.model.Project; import ghidra.framework.options.SaveState; import ghidra.program.model.address.*; import ghidra.util.exception.AssertException; @@ -200,15 +191,6 @@ public class OptionsEditorPanel extends JPanel { */ private Component getEditorComponent(Option option) { - // Special cases for library link/load options - if (option.getName().equals(AbstractLibrarySupportLoader.LINK_SEARCH_FOLDER_OPTION_NAME) || - option.getName().equals(AbstractLibrarySupportLoader.LIBRARY_DEST_FOLDER_OPTION_NAME)) { - return buildProjectFolderEditor(option); - } - if (option.getName().equals(AbstractLibrarySupportLoader.SYSTEM_LIBRARY_OPTION_NAME)) { - return buildPathsEditor(option); - } - Component customEditorComponent = option.getCustomEditorComponent(); if (customEditorComponent != null) { return customEditorComponent; @@ -243,59 +225,6 @@ public class OptionsEditorPanel extends JPanel { } } - private Component buildPathsEditor(Option option) { - JPanel panel = new JPanel(new BorderLayout()); - JButton button = new JButton("Edit Paths"); - button.addActionListener( - e -> DockingWindowManager.showDialog(panel, new LibraryPathsDialog())); - Boolean value = (Boolean) option.getValue(); - boolean initialState = value != null ? value : false; - GCheckBox jCheckBox = new GCheckBox("", initialState); - jCheckBox.addActionListener(e -> { - boolean b = jCheckBox.isSelected(); - option.setValue(b); - }); - panel.add(jCheckBox, BorderLayout.WEST); - panel.add(button, BorderLayout.EAST); - return panel; - } - - private Component buildProjectFolderEditor(Option option) { - Project project = AppInfo.getActiveProject(); - 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); - JButton button = new BrowseButton(); - button.addActionListener(e -> { - DataTreeDialog dataTreeDialog = - new DataTreeDialog(this, "Choose a project folder", CHOOSE_FOLDER); - String folderPath = lastFolderPath.isBlank() ? "/" : lastFolderPath; - dataTreeDialog.setSelectedFolder(project.getProjectData().getFolder(folderPath)); - dataTreeDialog.showComponent(); - DomainFolder folder = dataTreeDialog.getDomainFolder(); - if (folder != null) { - String newFolderPath = 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(button, BorderLayout.EAST); - return panel; - } - private Component getAddressSpaceEditorComponent(Option option) { if (addressFactoryService == null) { return null; @@ -320,49 +249,84 @@ public class OptionsEditorPanel extends JPanel { } private Component getStringEditorComponent(Option option) { + final SaveState state = option.getState(); + String defaultValue = (String) option.getValue(); + String value = + state != null ? state.getString(option.getName(), defaultValue) : defaultValue; + option.setValue(value); JTextField tf = new JTextField(5); tf.setName(option.getName()); - tf.getDocument().addDocumentListener(new ImporterDocumentListener(option, tf)); - String value = option.getValue() == null ? "" : (String) option.getValue(); + tf.getDocument().addDocumentListener(new ImporterDocumentListener(option, tf, state)); tf.setText(value); return tf; } private Component getHexLongEditorComponent(Option option) { + final SaveState state = option.getState(); + HexLong defaultValue = (HexLong) option.getValue(); + long value = state != null ? state.getLong(option.getName(), defaultValue.longValue()) + : defaultValue.longValue(); + option.setValue(new HexLong(value)); IntegerTextField field = new IntegerTextField(); - HexLong hexLong = (HexLong) option.getValue(); - long value = hexLong == null ? 0 : hexLong.longValue(); field.setValue(value); field.setHexMode(); - field.addChangeListener(e -> option.setValue(new HexLong(field.getLongValue()))); + field.addChangeListener(e -> { + option.setValue(new HexLong(field.getLongValue())); + if (state != null) { + state.putLong(option.getName(), field.getLongValue()); + } + }); return field.getComponent(); } private Component getIntegerEditorComponent(Option option) { + final SaveState state = option.getState(); + int defaultValue = (int) option.getValue(); + int value = state != null ? state.getInt(option.getName(), defaultValue) : defaultValue; + option.setValue(value); IntegerTextField field = new IntegerTextField(); - Integer value = (Integer) option.getValue(); - if (value != null) { - field.setValue(value); - } - field.addChangeListener(e -> option.setValue(field.getIntValue())); + field.setValue(value); + field.addChangeListener(e -> { + option.setValue(field.getIntValue()); + if (state != null) { + state.putInt(option.getName(), field.getIntValue()); + } + }); return field.getComponent(); } private Component getLongEditorComponent(Option option) { + final SaveState state = option.getState(); + long defaultValue = (long) option.getValue(); + long value = + state != null ? state.getLong(option.getName(), defaultValue) : defaultValue; + option.setValue(value); IntegerTextField field = new IntegerTextField(); - Long value = (Long) option.getValue(); field.setValue(value); - field.addChangeListener(e -> option.setValue(field.getLongValue())); + field.addChangeListener(e -> { + option.setValue(field.getLongValue()); + if (state != null) { + state.putLong(option.getName(), field.getLongValue()); + } + }); return field.getComponent(); } private Component getBooleanEditorComponent(Option option) { + final SaveState state = option.getState(); + boolean defaultValue = (boolean) option.getValue(); + boolean initialState = + state != null ? state.getBoolean(option.getName(), defaultValue) : defaultValue; + option.setValue(initialState); GCheckBox cb = new GCheckBox(); cb.setName(option.getName()); - Boolean b = (Boolean) option.getValue(); - boolean initialState = b != null ? b : false; cb.setSelected(initialState); - cb.addItemListener(e -> option.setValue(cb.isSelected())); + cb.addItemListener(e -> { + option.setValue(cb.isSelected()); + if (state != null) { + state.putBoolean(option.getName(), cb.isSelected()); + } + }); return cb; } @@ -383,16 +347,17 @@ public class OptionsEditorPanel extends JPanel { addressInput.addChangeListener(e -> option.setValue(addressInput.getAddress()));// addressInput.addActionListener(e -> option.setValue(addressInput.getAddress())); return addressInput; } - } class ImporterDocumentListener implements DocumentListener { private Option option; private JTextField textField; + private SaveState state; - ImporterDocumentListener(Option option, JTextField textField) { + ImporterDocumentListener(Option option, JTextField textField, SaveState state) { this.option = option; this.textField = textField; + this.state = state; } @Override @@ -413,5 +378,8 @@ class ImporterDocumentListener implements DocumentListener { private void updated() { String text = textField.getText(); option.setValue(text); + if (state != null) { + state.putString(option.getName(), text); + } } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/headless/AnalyzeHeadless.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/headless/AnalyzeHeadless.java index cc1ede4243..b60925091e 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/headless/AnalyzeHeadless.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/headless/AnalyzeHeadless.java @@ -23,6 +23,7 @@ import java.util.*; import generic.stl.Pair; import ghidra.*; +import ghidra.app.util.importer.LibrarySearchPathManager; import ghidra.app.util.opinion.Loader; import ghidra.framework.*; import ghidra.framework.model.DomainFolder; @@ -341,6 +342,9 @@ public class AnalyzeHeadless implements GhidraLaunchable { else if ("-okToDelete".equalsIgnoreCase(args[argi])) { options.setOkToDelete(true); } + else if (checkArgument("-librarySearchPaths", args, argi)) { + LibrarySearchPathManager.setLibraryPaths(args[++argi].split(";")); + } else { throw new InvalidInputException("Bad argument: " + arg); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/importer/AutoImporter.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/importer/AutoImporter.java index a99484bc09..068962be54 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/importer/AutoImporter.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/importer/AutoImporter.java @@ -17,8 +17,7 @@ package ghidra.app.util.importer; import java.io.File; import java.io.IOException; -import java.util.ArrayList; -import java.util.List; +import java.util.*; import java.util.function.Predicate; import generic.stl.Pair; @@ -718,6 +717,8 @@ public final class AutoImporter { Msg.info(AutoImporter.class, "Using Loader: " + loadSpec.getLoader().getName()); Msg.info(AutoImporter.class, "Using Language/Compiler: " + loadSpec.getLanguageCompilerSpec()); + Msg.info(AutoImporter.class, "Using Library Search Path: " + + Arrays.toString(LibrarySearchPathManager.getLibraryPaths())); LoadResults extends DomainObject> loadResults = loadSpec.getLoader() .load(provider, importName, project, projectFolderPath, loadSpec, loaderOptions, messageLog, consumer, monitor); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/importer/DomainFolderOption.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/importer/DomainFolderOption.java new file mode 100644 index 0000000000..9631bbda7d --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/importer/DomainFolderOption.java @@ -0,0 +1,90 @@ +/* ### + * 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.importer; + +import static ghidra.framework.main.DataTreeDialogType.*; + +import java.awt.BorderLayout; +import java.awt.Component; + +import javax.swing.*; + +import docking.widgets.button.BrowseButton; +import ghidra.app.util.Option; +import ghidra.app.util.opinion.Loader; +import ghidra.framework.main.AppInfo; +import ghidra.framework.main.DataTreeDialog; +import ghidra.framework.model.DomainFolder; +import ghidra.framework.options.SaveState; + +/** + * An {@link Option} used to specify a {@link DomainFolder} + */ +public class DomainFolderOption extends Option { + + /** + * Creates a new {@link DomainFolderOption} + * + * @param name The name of the 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); + } + + @Override + public Component getCustomEditorComponent() { + final SaveState state = getState(); + String defaultValue = (String) getValue(); + String lastFolderPath = + state != null ? state.getString(getName(), defaultValue) : defaultValue; + setValue(lastFolderPath); + JTextField textField = new JTextField(lastFolderPath); + textField.setEditable(false); + JButton button = new BrowseButton(); + button.addActionListener(e -> { + DataTreeDialog dataTreeDialog = + new DataTreeDialog(null, "Choose a project folder", CHOOSE_FOLDER); + String folderPath = lastFolderPath.isBlank() ? "/" : lastFolderPath; + dataTreeDialog.setSelectedFolder( + AppInfo.getActiveProject().getProjectData().getFolder(folderPath)); + dataTreeDialog.showComponent(); + DomainFolder folder = dataTreeDialog.getDomainFolder(); + if (folder != null) { + String newFolderPath = folder.getPathname(); + textField.setText(newFolderPath); + setValue(newFolderPath); + if (state != null) { + state.putString(getName(), newFolderPath); + } + } + }); + JPanel panel = new JPanel(new BorderLayout()); + panel.add(textField, BorderLayout.CENTER); + panel.add(button, BorderLayout.EAST); + return panel; + } + + @Override + public Class> getValueClass() { + return String.class; + } + + @Override + public Option copy() { + return new DomainFolderOption(getName(), getArg()); + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/importer/LibrarySearchPathDummyOption.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/importer/LibrarySearchPathDummyOption.java new file mode 100644 index 0000000000..8aac3a6513 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/importer/LibrarySearchPathDummyOption.java @@ -0,0 +1,59 @@ +/* ### + * 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.importer; + +import java.awt.Component; + +import javax.swing.JButton; + +import docking.DockingWindowManager; +import ghidra.app.util.Option; +import ghidra.app.util.opinion.LibraryPathsDialog; + +/** + * A dummy {@link Option} used to render a button that will allow the user to edit the global + * list of library search paths + */ +public class LibrarySearchPathDummyOption extends Option { + + /** + * Creates a new {@link LibrarySearchPathDummyOption} + * + * @param name The name of the option + */ + public LibrarySearchPathDummyOption(String name) { + super(name, null); + } + + @Override + public Component getCustomEditorComponent() { + JButton button = new JButton("Edit Paths"); + button.addActionListener(e -> { + DockingWindowManager.showDialog(null, new LibraryPathsDialog()); + }); + return button; + } + + @Override + public Class> getValueClass() { + return Object.class; + } + + @Override + public Option copy() { + return new LibrarySearchPathDummyOption(getName()); + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/importer/LibrarySearchPathManager.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/importer/LibrarySearchPathManager.java index 739e70ca65..c3506c3178 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/importer/LibrarySearchPathManager.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/importer/LibrarySearchPathManager.java @@ -20,70 +20,65 @@ import java.io.IOException; import java.net.MalformedURLException; import java.util.*; -import ghidra.formats.gfilesystem.FSRL; -import ghidra.formats.gfilesystem.FileSystemService; +import ghidra.app.util.bin.ByteProvider; +import ghidra.app.util.opinion.Loader; +import ghidra.formats.gfilesystem.*; import ghidra.framework.Platform; +import ghidra.framework.main.AppInfo; +import ghidra.framework.model.Project; +import ghidra.framework.options.SaveState; import ghidra.util.exception.CancelledException; import ghidra.util.task.TaskMonitor; /** - * A simple class for managing the library search path - * and avoiding duplicate directories. + * A simple class for managing the library search path and avoiding duplicate directories. */ public class LibrarySearchPathManager { - private static List
- Click the button
-- Select a directory from the file chooser
+- Select a directory or container file from the file chooser, or the program's + import location if "." is not present in the list already
- Click the "Select Directory" button
pathList = createPathList(); - private static boolean hasBeenRestored; - - private static List createPathList() { - pathList = new ArrayList<>(); - loadJavaLibraryPath(); - return pathList; - } - - private static void loadJavaLibraryPath() { - List paths = Platform.CURRENT_PLATFORM.getAdditionalLibraryPaths(); - for (String path : paths) { - addPath(path); - } - - String libpath = System.getProperty("java.library.path"); - String libpathSep = System.getProperty("path.separator"); - - StringTokenizer nizer = new StringTokenizer(libpath, libpathSep); - while (nizer.hasMoreTokens()) { - String path = nizer.nextToken(); - addPath(path); - } - } + private static final String LIBRARY_SEARCH_PATH_STATE_NAME = "Library Search Paths"; + private static Set pathSet = initialize(); /** - * Returns an array of directories to search for libraries - * @return an array of directories to search for libraries + * Returns an array of library search paths + * + * @return an array of library search paths */ - public static String[] getLibraryPaths() { - String[] paths = new String[pathList.size()]; - pathList.toArray(paths); + public static synchronized String[] getLibraryPaths() { + String[] paths = new String[pathSet.size()]; + pathSet.toArray(paths); return paths; } /** * Returns a {@link List} of {@link FSRL}s to search for libraries + * + * @param provider The {@link ByteProvider} of the 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 List getLibraryFsrlList(MessageLog log, TaskMonitor monitor) - throws CancelledException { + public static synchronized List getLibraryFsrlList(ByteProvider provider, MessageLog log, + TaskMonitor monitor) throws CancelledException { FileSystemService fsService = FileSystemService.getInstance(); List fsrlList = new ArrayList<>(); - for (String path : pathList) { + for (String path : pathSet) { monitor.checkCancelled(); path = path.trim(); FSRL fsrl = null; try { - fsrl = FSRL.fromString(path); + if (path.equals(".")) { + FSRL providerFsrl = provider.getFSRL(); + if (providerFsrl != null) { + try (RefdFile fileRef = fsService.getRefdFile(providerFsrl, monitor)) { + GFile parentFile = fileRef.file.getParentFile(); + fsrl = parentFile.getFSRL(); + } + } + } + else { + fsrl = FSRL.fromString(path); + } } catch (MalformedURLException e) { try { @@ -96,6 +91,9 @@ public class LibrarySearchPathManager { log.appendException(e2); } } + catch (IOException e) { + log.appendException(e); + } if (fsrl != null) { fsrlList.add(fsrl); } @@ -104,85 +102,95 @@ public class LibrarySearchPathManager { } /** - * Sets the directories to search for libraries + * Sets the library search paths to the given array + * * @param paths the new library search paths */ - public static void setLibraryPaths(String[] paths) { - - pathList.clear(); - for (String path : paths) { - addPath(path); - } + public static synchronized void setLibraryPaths(String[] paths) { + pathSet.clear(); + pathSet.addAll(Arrays.asList(paths)); + saveState(); } /** - * Call this to restore paths that were previously persisted. If you really need to change - * the paths for the entire JVM, then call {@link #setLibraryPaths(String[])}. - * - * @param paths the paths to restore - */ - public static void restoreLibraryPaths(String[] paths) { - - if (hasBeenRestored) { - // - // We code that restores paths from tool config files. It is a mistake to do this - // every time we load a tool, as the values can get out-of-sync if tools do not - // save properly. Logically, we only need to restore once. - // - return; - } - - setLibraryPaths(paths); - } - - /** - * Adds the specified path to the end of the path search list. - * @param path the path to add + * Adds the specified library search path path to the end of the path search list + * + * @param path the library search path to add * @return true if the path was appended, false if the path was a duplicate */ - public static boolean addPath(String path) { - if (pathList.indexOf(path) == -1) { - pathList.add(path); - return true; + public static synchronized boolean addPath(String path) { + if (pathSet.contains(path)) { + return false; } - return false; + pathSet.add(path); + saveState(); + return true; } /** - * Adds the path at the specified index in path search list. - * @param index The index - * @param path the path to add - * @return true if the path was appended, false if the path was a duplicate + * Resets the library search path to the default values */ - public static boolean addPathAt(int index, String path) { - if (pathList.indexOf(path) == -1) { - pathList.add(index, path); - return true; + public static synchronized void reset() { + pathSet = loadDefaultPaths(); + saveState(); + } + + private LibrarySearchPathManager() { + // Prevent instantiation of utility class + } + + private static synchronized Set initialize() { + Set set = loadFromSavedState(); + if (set == null) { + set = loadDefaultPaths(); } - return false; + return set; } - /** - * Removes the path from the path search list. - * @param path the path the remove - * @return true if the path was removed, false if the path did not exist - */ - public static boolean removePath(String path) { - return pathList.remove(path); + private static synchronized Set loadDefaultPaths() { + Set set = new LinkedHashSet<>(); + + // Add program import location + set.add("."); + + // Add platform specific locations + Platform.CURRENT_PLATFORM.getAdditionalLibraryPaths().forEach(p -> set.add(p)); + + // Add Java library path locations + String libpath = System.getProperty("java.library.path"); + String libpathSep = System.getProperty("path.separator"); + StringTokenizer nizer = new StringTokenizer(libpath, libpathSep); + while (nizer.hasMoreTokens()) { + String path = nizer.nextToken(); + set.add(path); + } + + return set; } - /** - * Resets the library search path to match the system search paths. - */ - public static void reset() { - pathList.clear(); - loadJavaLibraryPath(); + private static synchronized Set loadFromSavedState() { + Project project = AppInfo.getActiveProject(); + if (project != null) { + SaveState saveState = project.getSaveableData(Loader.OPTIONS_PROJECT_SAVE_STATE_KEY); + if (saveState != null) { + String[] paths = saveState.getStrings(LIBRARY_SEARCH_PATH_STATE_NAME, null); + if (paths != null) { + return new LinkedHashSet (Arrays.asList(paths)); + } + } + } + return null; } - /** - * Clears all paths. - */ - public static void clear() { - pathList.clear(); + private static synchronized void saveState() { + Project project = AppInfo.getActiveProject(); + if (project != null) { + SaveState saveState = project.getSaveableData(Loader.OPTIONS_PROJECT_SAVE_STATE_KEY); + if (saveState == null) { + saveState = new SaveState(); + project.setSaveableData(Loader.OPTIONS_PROJECT_SAVE_STATE_KEY, saveState); + } + saveState.putStrings(LIBRARY_SEARCH_PATH_STATE_NAME, pathSet.toArray(new String[0])); + } } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/AbstractLibrarySupportLoader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/AbstractLibrarySupportLoader.java index f320158e2e..40d8f78e1d 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/AbstractLibrarySupportLoader.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/AbstractLibrarySupportLoader.java @@ -29,8 +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.importer.LibrarySearchPathManager; -import ghidra.app.util.importer.MessageLog; +import ghidra.app.util.importer.*; import ghidra.formats.gfilesystem.*; import ghidra.framework.model.*; import ghidra.program.model.address.Address; @@ -57,11 +56,10 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader public static final String LINK_SEARCH_FOLDER_OPTION_NAME = "Project Library Search Folder"; static final String LINK_SEARCH_FOLDER_OPTION_DEFAULT = ""; - public static final String LOCAL_LIBRARY_OPTION_NAME = "Load Local Libraries From Disk"; - static final boolean LOCAL_LIBRARY_OPTION_DEFAULT = false; + public static final String LOAD_LIBRARY_OPTION_NAME = "Load Libraries From Disk"; + static final boolean LOAD_LIBRARY_OPTION_DEFAULT = false; - public static final String SYSTEM_LIBRARY_OPTION_NAME = "Load System Libraries From Disk"; - static final boolean SYSTEM_LIBRARY_OPTION_DEFAULT = false; + public static final String LIBRARY_SEARCH_PATH_DUMMY_OPTION_NAME = "Library Search Paths"; public static final String DEPTH_OPTION_NAME = "Recursive Library Load Depth"; static final int DEPTH_OPTION_DEFAULT = 1; @@ -143,8 +141,7 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader if (loadedPrograms.isEmpty()) { return; } - if (isLinkExistingLibraries(options) || isLoadLocalLibraries(options) || - isLoadSystemLibraries(options)) { + if (isLinkExistingLibraries(options) || isLoadLibraries(options)) { String projectFolderPath = loadedPrograms.get(0).getProjectFolderPath(); List searchFolders = new ArrayList<>(); String destPath = getLibraryDestinationFolderPath(project, projectFolderPath, options); @@ -177,18 +174,19 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader DomainObject domainObject, boolean loadIntoProgram) { List