mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-02-16 07:30:16 +00:00
GP-4564: Improvements to library search paths and other loader options
This commit is contained in:
parent
4302fdc00d
commit
d3d60ea399
@ -89,7 +89,7 @@ public class ScriptPathsPropertyEditor extends AbstractTypedPropertyEditor<Strin
|
||||
protected class ScriptPathsPanel extends PathnameTablePanel {
|
||||
public ScriptPathsPanel(String[] paths, Callback resetCallback) {
|
||||
// disable edits, top/bottom irrelevant, unordered
|
||||
super(paths, resetCallback, false, false, false);
|
||||
super(paths, resetCallback, false, false, false, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -62,6 +62,7 @@ The Headless Analyzer can be useful when performing repetitive tasks on a projec
|
||||
<BR> [-p]
|
||||
<BR> [-commit ["<comment>"]]
|
||||
<BR> [-max-cpu <max cpu cores to use>]
|
||||
<BR> [-librarySearchPaths <path1>[;<path2>...]]
|
||||
<BR> [-loader <desired loader name>]
|
||||
</FONT>
|
||||
</P>
|
||||
|
@ -311,18 +311,8 @@
|
||||
<P>The project folder that will get searched for existing library programs. If left
|
||||
empty, the folder that the main program is being imported to will be searched.</P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H4>Load Local Libraries From Disk</H4>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<P>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 <A href="help/topics/ReferencesPlugin/References_from.htm#extRefs">external references</A>
|
||||
in these programs will be resolved.<BR>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H4>Load System Libraries From Disk</H4>
|
||||
<H4>Load Libraries From Disk</H4>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<P>Searches a user-defined path list to recursively resolve the external libraries used
|
||||
@ -696,8 +686,12 @@
|
||||
<H2><A name="Library_Paths"></A>Library Search Path</H2>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<P>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.</P>
|
||||
<P>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
|
||||
<A href="help/topics/FileSystemBrowserPlugin/FileSystemBrowserPlugin.html#FSB_Add_Library_Search_Path">File System Browser context menu</A>.
|
||||
</P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<P align="center"><IMG alt="" src="images/SearchPathsDialog.png"></P>
|
||||
@ -734,7 +728,8 @@
|
||||
<OL>
|
||||
<LI>Click the <IMG alt="" src="images/Plus.png"> button</LI>
|
||||
|
||||
<LI>Select a directory from the file chooser</LI>
|
||||
<LI>Select a directory or container file from the file chooser, or the program's
|
||||
import location if "." is not present in the list already</LI>
|
||||
|
||||
<LI>Click the "Select Directory" button</LI>
|
||||
</OL>
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
@ -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<String> pathList = createPathList();
|
||||
|
||||
private static boolean hasBeenRestored;
|
||||
|
||||
private static List<String> createPathList() {
|
||||
pathList = new ArrayList<>();
|
||||
loadJavaLibraryPath();
|
||||
return pathList;
|
||||
}
|
||||
|
||||
private static void loadJavaLibraryPath() {
|
||||
List<String> 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<String> 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<FSRL> getLibraryFsrlList(MessageLog log, TaskMonitor monitor)
|
||||
throws CancelledException {
|
||||
public static synchronized List<FSRL> getLibraryFsrlList(ByteProvider provider, MessageLog log,
|
||||
TaskMonitor monitor) throws CancelledException {
|
||||
FileSystemService fsService = FileSystemService.getInstance();
|
||||
List<FSRL> 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 <b>for the entire JVM</b>, 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<String> initialize() {
|
||||
Set<String> 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<String> loadDefaultPaths() {
|
||||
Set<String> 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<String> 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<String>(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]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<DomainFolder> searchFolders = new ArrayList<>();
|
||||
String destPath = getLibraryDestinationFolderPath(project, projectFolderPath, options);
|
||||
@ -177,18 +174,19 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
|
||||
DomainObject domainObject, boolean loadIntoProgram) {
|
||||
List<Option> list =
|
||||
super.getDefaultOptions(provider, loadSpec, domainObject, loadIntoProgram);
|
||||
|
||||
list.add(new Option(LINK_EXISTING_OPTION_NAME, LINK_EXISTING_OPTION_DEFAULT, Boolean.class,
|
||||
Loader.COMMAND_LINE_ARG_PREFIX + "-linkExistingProjectLibraries"));
|
||||
list.add(new Option(LINK_SEARCH_FOLDER_OPTION_NAME, LINK_SEARCH_FOLDER_OPTION_DEFAULT,
|
||||
String.class, Loader.COMMAND_LINE_ARG_PREFIX + "-projectLibrarySearchFolder"));
|
||||
list.add(new Option(LOCAL_LIBRARY_OPTION_NAME, LOCAL_LIBRARY_OPTION_DEFAULT, Boolean.class,
|
||||
Loader.COMMAND_LINE_ARG_PREFIX + "-loadLocalLibraries"));
|
||||
list.add(new Option(SYSTEM_LIBRARY_OPTION_NAME, SYSTEM_LIBRARY_OPTION_DEFAULT, Boolean.class,
|
||||
Loader.COMMAND_LINE_ARG_PREFIX + "-loadSystemLibraries"));
|
||||
list.add(new DomainFolderOption(LINK_SEARCH_FOLDER_OPTION_NAME,
|
||||
Loader.COMMAND_LINE_ARG_PREFIX + "-projectLibrarySearchFolder"));
|
||||
list.add(new Option(LOAD_LIBRARY_OPTION_NAME, LOAD_LIBRARY_OPTION_DEFAULT, Boolean.class,
|
||||
Loader.COMMAND_LINE_ARG_PREFIX + "-loadLibraries"));
|
||||
list.add(new LibrarySearchPathDummyOption(LIBRARY_SEARCH_PATH_DUMMY_OPTION_NAME));
|
||||
list.add(new Option(DEPTH_OPTION_NAME, DEPTH_OPTION_DEFAULT, Integer.class,
|
||||
Loader.COMMAND_LINE_ARG_PREFIX + "-libraryLoadDepth"));
|
||||
list.add(new Option(LIBRARY_DEST_FOLDER_OPTION_NAME, LIBRARY_DEST_FOLDER_OPTION_DEFAULT,
|
||||
String.class, Loader.COMMAND_LINE_ARG_PREFIX + "-libraryDestinationFolder"));
|
||||
list.add(new DomainFolderOption(LIBRARY_DEST_FOLDER_OPTION_NAME,
|
||||
Loader.COMMAND_LINE_ARG_PREFIX + "-libraryDestinationFolder"));
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
@ -199,8 +197,7 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
|
||||
for (Option option : options) {
|
||||
String name = option.getName();
|
||||
if (name.equals(LINK_EXISTING_OPTION_NAME) ||
|
||||
name.equals(LOCAL_LIBRARY_OPTION_NAME) ||
|
||||
name.equals(SYSTEM_LIBRARY_OPTION_NAME)) {
|
||||
name.equals(LOAD_LIBRARY_OPTION_NAME)) {
|
||||
if (!Boolean.class.isAssignableFrom(option.getValueClass())) {
|
||||
return "Invalid type for option: " + name + " - " + option.getValueClass();
|
||||
}
|
||||
@ -271,27 +268,14 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks to see if local libraries should be loaded. Local libraries are libraries that live
|
||||
* in the same directory as the imported program.
|
||||
* Checks to see if libraries from disk should be loaded
|
||||
*
|
||||
* @param options a {@link List} of {@link Option}s
|
||||
* @return True if local libraries should be loaded; otherwise, false
|
||||
* @return True if libraries from disk should be loaded; otherwise, false
|
||||
*/
|
||||
protected boolean isLoadLocalLibraries(List<Option> options) {
|
||||
return OptionUtils.getOption(LOCAL_LIBRARY_OPTION_NAME, options,
|
||||
LOCAL_LIBRARY_OPTION_DEFAULT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks to see if system libraries should be loaded. System libraries are libraries that live
|
||||
* in the directories specified in the GUI path list.
|
||||
*
|
||||
* @param options a {@link List} of {@link Option}s
|
||||
* @return True if system libraries should be loaded; otherwise, false
|
||||
*/
|
||||
protected boolean isLoadSystemLibraries(List<Option> options) {
|
||||
return OptionUtils.getOption(SYSTEM_LIBRARY_OPTION_NAME, options,
|
||||
SYSTEM_LIBRARY_OPTION_DEFAULT);
|
||||
protected boolean isLoadLibraries(List<Option> options) {
|
||||
return OptionUtils.getOption(LOAD_LIBRARY_OPTION_NAME, options,
|
||||
LOAD_LIBRARY_OPTION_DEFAULT);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -348,7 +332,7 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
|
||||
if (project == null || libraryDestinationFolderPath == null) {
|
||||
return null;
|
||||
}
|
||||
if (!isLoadLocalLibraries(options) && !isLoadSystemLibraries(options)) {
|
||||
if (!isLoadLibraries(options)) {
|
||||
return null;
|
||||
}
|
||||
return project.getProjectData().getFolder(libraryDestinationFolderPath);
|
||||
@ -472,14 +456,11 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
|
||||
Set<String> processed = new TreeSet<>(getLibraryNameComparator());
|
||||
Queue<UnprocessedLibrary> unprocessed =
|
||||
createUnprocessedQueue(libraryNameList, getLibraryLoadDepth(options));
|
||||
boolean loadLocalLibraries = isLoadLocalLibraries(options);
|
||||
boolean loadSystemLibraries = isLoadSystemLibraries(options);
|
||||
boolean loadLibraries = isLoadLibraries(options);
|
||||
List<FileSystemSearchPath> customSearchPaths =
|
||||
getCustomLibrarySearchPaths(provider, options, log, monitor);
|
||||
List<FileSystemSearchPath> localSearchPaths =
|
||||
getLocalLibrarySearchPaths(provider, options, log, monitor);
|
||||
List<FileSystemSearchPath> systemSearchPaths =
|
||||
getSystemLibrarySearchPaths(options, log, monitor);
|
||||
List<FileSystemSearchPath> searchPaths =
|
||||
getLibrarySearchPaths(provider, options, log, monitor);
|
||||
DomainFolder linkSearchFolder = getLinkSearchFolder(project, projectFolderPath, options);
|
||||
String libraryDestFolderPath =
|
||||
getLibraryDestinationFolderPath(project, projectFolderPath, options);
|
||||
@ -507,9 +488,8 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
|
||||
log.appendMsg("Found %s in %s...".formatted(libraryName, linkSearchFolder));
|
||||
log.appendMsg("------------------------------------------------\n");
|
||||
}
|
||||
else if (!customSearchPaths.isEmpty() || !localSearchPaths.isEmpty() ||
|
||||
!systemSearchPaths.isEmpty()) {
|
||||
// Note that it is possible to have local (or system) search paths with those
|
||||
else if (!customSearchPaths.isEmpty() || !searchPaths.isEmpty()) {
|
||||
// Note that it is possible to have search paths with those
|
||||
// options turned off (if shouldSearchAllPaths() is overridden to return true).
|
||||
// In this case, we still want to process those libraries, but we
|
||||
// do not want to save them, so they can be released.
|
||||
@ -528,34 +508,15 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
|
||||
loadedPrograms.add(loadedLibrary);
|
||||
}
|
||||
}
|
||||
if (!loaded && !localSearchPaths.isEmpty()) {
|
||||
log.appendMsg("Searching %d local path%s for library %s...".formatted(
|
||||
localSearchPaths.size(), localSearchPaths.size() > 1 ? "s" : "",
|
||||
libraryName));
|
||||
if (!loaded && !searchPaths.isEmpty()) {
|
||||
log.appendMsg("Searching %d path%s for library %s...".formatted(
|
||||
searchPaths.size(), searchPaths.size() > 1 ? "s" : "", libraryName));
|
||||
Loaded<Program> loadedLibrary = loadLibraryFromSearchPaths(libraryName,
|
||||
provider, localSearchPaths, libraryDestFolderPath, unprocessed, depth,
|
||||
provider, searchPaths, libraryDestFolderPath, unprocessed, depth,
|
||||
desiredLoadSpec, options, log, consumer, monitor);
|
||||
if (loadedLibrary != null) {
|
||||
found = true;
|
||||
if (loadLocalLibraries) {
|
||||
loaded = true;
|
||||
loadedPrograms.add(loadedLibrary);
|
||||
}
|
||||
else {
|
||||
loadedLibrary.release(consumer);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!loaded && !systemSearchPaths.isEmpty()) {
|
||||
log.appendMsg("Searching %d system path%s for library %s...".formatted(
|
||||
systemSearchPaths.size(), systemSearchPaths.size() > 1 ? "s" : "",
|
||||
libraryName));
|
||||
Loaded<Program> loadedLibrary = loadLibraryFromSearchPaths(libraryName,
|
||||
provider, systemSearchPaths, libraryDestFolderPath, unprocessed, depth,
|
||||
desiredLoadSpec, options, log, consumer, monitor);
|
||||
if (loadedLibrary != null) {
|
||||
found = true;
|
||||
if (loadSystemLibraries) {
|
||||
if (loadLibraries) {
|
||||
loaded = true;
|
||||
loadedPrograms.add(loadedLibrary);
|
||||
}
|
||||
@ -586,7 +547,7 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
|
||||
if (!success) {
|
||||
release(loadedPrograms, consumer);
|
||||
}
|
||||
Stream.of(customSearchPaths, localSearchPaths, systemSearchPaths)
|
||||
Stream.of(customSearchPaths, searchPaths)
|
||||
.flatMap(Collection::stream)
|
||||
.forEach(fsSearchPath -> {
|
||||
if (!fsSearchPath.fsRef().isClosed()) {
|
||||
@ -1068,52 +1029,20 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a {@link List} of priority-ordered local {@link FileSystemSearchPath}s used to search
|
||||
* for libraries
|
||||
* Gets a {@link List} of priority-ordered {@link FileSystemSearchPath}s used to search for
|
||||
* libraries
|
||||
*
|
||||
* @param provider The {@link ByteProvider} of the program being loaded
|
||||
* @param options The options
|
||||
* @param log The log
|
||||
* @param monitor A cancelable task monitor
|
||||
* @return A {@link List} of priority-ordered local {@link FileSystemSearchPath}s used to
|
||||
* search for libraries
|
||||
* @return A {@link List} of priority-ordered {@link FileSystemSearchPath}s used to search for
|
||||
* libraries
|
||||
* @throws CancelledException if the user cancelled the load
|
||||
*/
|
||||
private List<FileSystemSearchPath> getLocalLibrarySearchPaths(ByteProvider provider,
|
||||
private List<FileSystemSearchPath> getLibrarySearchPaths(ByteProvider provider,
|
||||
List<Option> options, MessageLog log, TaskMonitor monitor) throws CancelledException {
|
||||
if (!isLoadLocalLibraries(options) && !shouldSearchAllPaths(options)) {
|
||||
return List.of();
|
||||
}
|
||||
List<FileSystemSearchPath> result = new ArrayList<>();
|
||||
FileSystemService fsService = FileSystemService.getInstance();
|
||||
FSRL providerFsrl = provider.getFSRL();
|
||||
if (providerFsrl != null) {
|
||||
try (RefdFile fileRef = fsService.getRefdFile(providerFsrl, monitor)) {
|
||||
GFile parentFile = fileRef.file.getParentFile();
|
||||
File f = new File(parentFile.getPath()); // File API will sanitize Windows-style paths
|
||||
result.add(new FileSystemSearchPath(fileRef.fsRef.dup(), f.toPath()));
|
||||
}
|
||||
catch (IOException e) {
|
||||
log.appendException(e);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a {@link List} of priority-ordered system {@link FileSystemSearchPath}s used to search
|
||||
* for libraries
|
||||
*
|
||||
* @param options The options
|
||||
* @param log The log
|
||||
* @param monitor A cancelable task monitor
|
||||
* @return A {@link List} of priority-ordered system {@link FileSystemSearchPath}s used to
|
||||
* search for libraries
|
||||
* @throws CancelledException if the user cancelled the load
|
||||
*/
|
||||
private List<FileSystemSearchPath> getSystemLibrarySearchPaths(List<Option> options,
|
||||
MessageLog log, TaskMonitor monitor) throws CancelledException {
|
||||
if (!isLoadSystemLibraries(options) && !shouldSearchAllPaths(options)) {
|
||||
if (!isLoadLibraries(options) && !shouldSearchAllPaths(options)) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
@ -1121,7 +1050,7 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
|
||||
List<FileSystemSearchPath> result = new ArrayList<>();
|
||||
boolean success = false;
|
||||
try {
|
||||
for (FSRL fsrl : LibrarySearchPathManager.getLibraryFsrlList(log, monitor)) {
|
||||
for (FSRL fsrl : LibrarySearchPathManager.getLibraryFsrlList(provider, log, monitor)) {
|
||||
if (fsService.isLocal(fsrl)) {
|
||||
try {
|
||||
FileSystemRef fileRef =
|
||||
|
@ -44,7 +44,7 @@ public class LibraryPathsDialog extends AbstractPathsDialog {
|
||||
protected PathnameTablePanel newPathnameTablePanel() {
|
||||
// disable edits, add to top, ordered
|
||||
PathnameTablePanel panel =
|
||||
new PathnameTablePanel(loadPaths(), this::reset, false, true, true);
|
||||
new PathnameTablePanel(loadPaths(), this::reset, false, true, true, true);
|
||||
panel.setFileChooserProperties("Select Directory or Filesystem",
|
||||
"LibrarySearchDirectory", GhidraFileChooserMode.FILES_AND_DIRECTORIES, false, null);
|
||||
return panel;
|
||||
|
@ -34,7 +34,6 @@ 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.importer.LibrarySearchPathManager;
|
||||
import ghidra.app.util.opinion.LoaderMap;
|
||||
import ghidra.app.util.opinion.LoaderService;
|
||||
import ghidra.formats.gfilesystem.FSRL;
|
||||
@ -44,7 +43,6 @@ import ghidra.formats.gfilesystem.FileSystemService;
|
||||
import ghidra.framework.main.*;
|
||||
import ghidra.framework.main.datatree.*;
|
||||
import ghidra.framework.model.*;
|
||||
import ghidra.framework.options.SaveState;
|
||||
import ghidra.framework.options.ToolOptions;
|
||||
import ghidra.framework.plugintool.*;
|
||||
import ghidra.framework.plugintool.util.PluginStatus;
|
||||
@ -119,23 +117,6 @@ public class ImporterPlugin extends Plugin
|
||||
setupBatchImportAction();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readConfigState(SaveState saveState) {
|
||||
super.readConfigState(saveState);
|
||||
String[] paths = saveState.getStrings("library search paths", null);
|
||||
if (paths != null) {
|
||||
LibrarySearchPathManager.setLibraryPaths(paths);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeConfigState(SaveState saveState) {
|
||||
super.writeConfigState(saveState);
|
||||
|
||||
String[] paths = LibrarySearchPathManager.getLibraryPaths();
|
||||
saveState.putStrings("library search paths", paths);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void dispose() {
|
||||
super.dispose();
|
||||
|
@ -97,12 +97,7 @@ public class DyldCacheExtractLoader extends MachoLoader {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isLoadLocalLibraries(List<Option> options) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isLoadSystemLibraries(List<Option> options) {
|
||||
protected boolean isLoadLibraries(List<Option> options) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -97,12 +97,7 @@ public class MachoFileSetExtractLoader extends MachoLoader {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isLoadLocalLibraries(List<Option> options) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isLoadSystemLibraries(List<Option> options) {
|
||||
protected boolean isLoadLibraries(List<Option> options) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -57,6 +57,7 @@ public class PathnameTablePanel extends JPanel {
|
||||
private GhidraFileFilter filter;
|
||||
private boolean addToTop;
|
||||
private boolean ordered;
|
||||
private boolean supportsDotPath;
|
||||
|
||||
private Callback resetCallback;
|
||||
|
||||
@ -95,12 +96,15 @@ public class PathnameTablePanel extends JPanel {
|
||||
* false.
|
||||
* @param ordered true if the order of entries matters. If so, up and down buttons are provided
|
||||
* so the user may arrange the entries. If not, entries are sorted alphabetically.
|
||||
* @param supportsDotPath true if the add button should support adding the "." path. If so,
|
||||
* the user will be prompted to choose from a file browser, or adding ".".
|
||||
*/
|
||||
public PathnameTablePanel(String[] paths, Callback resetCallback, boolean enableEdits,
|
||||
boolean addToTop, boolean ordered) {
|
||||
boolean addToTop, boolean ordered, boolean supportsDotPath) {
|
||||
super(new BorderLayout(5, 5));
|
||||
this.addToTop = addToTop;
|
||||
this.ordered = ordered;
|
||||
this.supportsDotPath = supportsDotPath;
|
||||
this.resetCallback = resetCallback;
|
||||
tableModel = new PathnameTableModel(paths, enableEdits);
|
||||
create();
|
||||
@ -316,6 +320,17 @@ public class PathnameTablePanel extends JPanel {
|
||||
|
||||
private void add() {
|
||||
|
||||
if (supportsDotPath && !Arrays.stream(getPaths()).anyMatch(p -> p.equals("."))) {
|
||||
int selection =
|
||||
OptionDialog.showOptionNoCancelDialog(this, "Add Path", "Choose how to add a path:",
|
||||
"File Chooser", "Program's Import Location", OptionDialog.QUESTION_MESSAGE);
|
||||
|
||||
if (selection == OptionDialog.OPTION_TWO) {
|
||||
tableModel.addPaths(new String[] { "." }, addToTop, !ordered);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
GhidraFileChooser fileChooser = new GhidraFileChooser(this);
|
||||
fileChooser.setMultiSelectionEnabled(allowMultiFileSelection);
|
||||
fileChooser.setFileSelectionMode(fileChooserMode);
|
||||
@ -363,7 +378,7 @@ public class PathnameTablePanel extends JPanel {
|
||||
String confirmation = """
|
||||
<html><body width="200px">
|
||||
Are you sure you would like to reset the paths to the default list?
|
||||
This will remove all paths manually added.
|
||||
This will remove all paths manually added and cannot be later cancelled.
|
||||
</html>""";
|
||||
String header = "Reset Paths?";
|
||||
|
||||
|
@ -130,6 +130,7 @@ The Headless Analyzer uses the command-line parameters discussed below. See <a h
|
||||
[<a href="#commit">-commit ["<comment>"]</a>]
|
||||
[<a href="#okToDelete">-okToDelete</a>]
|
||||
[<a href="#max-cpu">-max-cpu <max cpu cores to use></a>]
|
||||
[<a href="#librarySearchPaths">-librarySearchPaths <path1>[;<path2>...]</a>]
|
||||
[<a href="#loader">-loader <desired loader name></a>]
|
||||
|
||||
</PRE>
|
||||
@ -583,6 +584,14 @@ The Headless Analyzer uses the command-line parameters discussed below. See <a h
|
||||
</LI>
|
||||
|
||||
<br><br>
|
||||
|
||||
<LI>
|
||||
<a name="librarySearchPaths"><typewriter>-librarySearchPaths <path1>[;<path2>...]</typewriter></a><br>
|
||||
Specifies an ordered list of library search paths to use during import instead of the default.
|
||||
Search paths may be either full system paths or "FSRLs".
|
||||
</LI>
|
||||
|
||||
<br><br>
|
||||
|
||||
<LI>
|
||||
<a name="loader"><typewriter>-loader <desired loader name></typewriter></a><br>
|
||||
@ -606,8 +615,7 @@ The Headless Analyzer uses the command-line parameters discussed below. See <a h
|
||||
<LI><typewriter>-loader-anchorLabels <true|false></typewriter></LI>
|
||||
<LI><typewriter>-loader-linkExistingProjectLibraries <true|false></typewriter></LI>
|
||||
<LI><typewriter>-loader-projectLibrarySearchFolder <project path></typewriter></LI>
|
||||
<LI><typewriter>-loader-loadLocalLibraries <true|false></typewriter></LI>
|
||||
<LI><typewriter>-loader-loadSystemLibraries <true|false></typewriter></LI>
|
||||
<LI><typewriter>-loader-loadLibraries <true|false></typewriter></LI>
|
||||
<LI><typewriter>-loader-libraryLoadDepth <depth></typewriter></LI>
|
||||
<LI><typewriter>-loader-libraryDestinationFolder <project path></typewriter></LI>
|
||||
<LI><typewriter>-loader-applyRelocations <true|false></typewriter></LI>
|
||||
@ -623,8 +631,7 @@ The Headless Analyzer uses the command-line parameters discussed below. See <a h
|
||||
<LI><typewriter>-loader-anchorLabels <true|false></typewriter></LI>
|
||||
<LI><typewriter>-loader-linkExistingProjectLibraries <true|false></typewriter></LI>
|
||||
<LI><typewriter>-loader-projectLibrarySearchFolder <project path></typewriter></LI>
|
||||
<LI><typewriter>-loader-loadLocalLibraries <true|false></typewriter></LI>
|
||||
<LI><typewriter>-loader-loadSystemLibraries <true|false></typewriter></LI>
|
||||
<LI><typewriter>-loader-loadLibraries <true|false></typewriter></LI>
|
||||
<LI><typewriter>-loader-libraryLoadDepth <depth></typewriter></LI>
|
||||
<LI><typewriter>-loader-libraryDestinationFolder <project path></typewriter></LI>
|
||||
<LI><typewriter>-loader-ordinalLookup <true|false></typewriter></LI>
|
||||
@ -637,8 +644,7 @@ The Headless Analyzer uses the command-line parameters discussed below. See <a h
|
||||
<LI><typewriter>-loader-anchorLabels <true|false></typewriter></LI>
|
||||
<LI><typewriter>-loader-linkExistingProjectLibraries <true|false></typewriter></LI>
|
||||
<LI><typewriter>-loader-projectLibrarySearchFolder <project path></typewriter></LI>
|
||||
<LI><typewriter>-loader-loadLocalLibraries <true|false></typewriter></LI>
|
||||
<LI><typewriter>-loader-loadSystemLibraries <true|false></typewriter></LI>
|
||||
<LI><typewriter>-loader-loadLibraries <true|false></typewriter></LI>
|
||||
<LI><typewriter>-loader-libraryLoadDepth <depth></typewriter></LI>
|
||||
<LI><typewriter>-loader-libraryDestinationFolder <project path></typewriter></LI>
|
||||
</UL>
|
||||
|
@ -59,7 +59,7 @@ public class PathnameTablePanelTest extends AbstractDockingTest {
|
||||
public void setUp() throws Exception {
|
||||
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
|
||||
// enable edits, add to bottom, ordered
|
||||
panel = new PathnameTablePanel(tablePaths, () -> reset(), true, false, true);
|
||||
panel = new PathnameTablePanel(tablePaths, () -> reset(), true, false, true, false);
|
||||
table = panel.getTable();
|
||||
frame = new JFrame("Test");
|
||||
frame.getContentPane().add(panel);
|
||||
|
@ -35,7 +35,7 @@
|
||||
[-propertiesPath "<path1>[;<path2>...]"]
|
||||
[-log <path to log file>] [-scriptlog <path to script log file>]
|
||||
[-overwrite] [-recursive [<depth>]] [-readOnly] [-deleteProject]
|
||||
[-noanalysis]
|
||||
[-noanalysis] [-librarySearchPaths <path1>[;<path2>...]]
|
||||
[-processor <languageID>] [-cspec <compilerSpecID>]
|
||||
[-analysisTimeoutPerFile <timeout in seconds>]
|
||||
[-keystore <KeystorePath>] [-connect [<userID>]]
|
||||
|
Loading…
Reference in New Issue
Block a user