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 ab09bb352b..7f0b8f603d 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,7 +17,8 @@ package ghidra.app.util.importer; import java.io.File; import java.io.IOException; -import java.util.*; +import java.util.ArrayList; +import java.util.List; import java.util.function.Predicate; import generic.stl.Pair; @@ -202,8 +203,7 @@ public final class AutoImporter { private static LoadSpec getLoadSpec(Predicate loaderFilter, LoadSpecChooser loadSpecChooser, ByteProvider provider) { - Map> loadMap = - LoaderService.getSupportedLoadSpecs(provider, loaderFilter); + LoadMap loadMap = LoaderService.getSupportedLoadSpecs(provider, loaderFilter); LoadSpec loadSpec = loadSpecChooser.choose(loadMap); if (loadSpec != null) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/importer/CsHintLoadSpecChooser.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/importer/CsHintLoadSpecChooser.java new file mode 100644 index 0000000000..befb71c55c --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/importer/CsHintLoadSpecChooser.java @@ -0,0 +1,57 @@ +/* ### + * 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 ghidra.app.util.opinion.*; +import ghidra.program.model.lang.CompilerSpec; +import ghidra.program.model.lang.CompilerSpecID; + +/** + * Chooses a {@link LoadSpec} for a {@link Loader} to use based on a provided {@link CompilerSpec}. + */ +public class CsHintLoadSpecChooser implements LoadSpecChooser { + private final CompilerSpecID compilerSpecID; + + /** + * Creates a new {@link CsHintLoadSpecChooser} + * + * @param compilerSpecID The {@link CompilerSpecID} to use (should not be null) + */ + public CsHintLoadSpecChooser(CompilerSpecID compilerSpecID) { + this.compilerSpecID = compilerSpecID; + } + + /** + * Creates a new {@link CsHintLoadSpecChooser} + * + * @param compilerSpecID The {@link CompilerSpecID} to use (should not be null) + */ + public CsHintLoadSpecChooser(String compilerSpecID) { + this(new CompilerSpecID(compilerSpecID)); + } + + @Override + public LoadSpec choose(LoadMap loadMap) { + + return loadMap.values() + .stream() + .flatMap(loadSpec -> loadSpec.stream()) + .filter(loadSpec -> loadSpec.getLanguageCompilerSpec().compilerSpecID + .equals(compilerSpecID)) + .findFirst() + .orElse(null); + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/importer/LcsHintLoadSpecChooser.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/importer/LcsHintLoadSpecChooser.java index 3f8b48960f..b06216822c 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/importer/LcsHintLoadSpecChooser.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/importer/LcsHintLoadSpecChooser.java @@ -16,7 +16,6 @@ package ghidra.app.util.importer; import java.util.Collection; -import java.util.Map; import ghidra.app.util.opinion.*; import ghidra.program.model.lang.*; @@ -46,22 +45,11 @@ public class LcsHintLoadSpecChooser implements LoadSpecChooser { } @Override - public LoadSpec choose(Map> loadMap) { + public LoadSpec choose(LoadMap loadMap) { - // We need to reduce the set of matching loaders down to one. The only requirement is that - // if we do reduce, make sure we don't reduce it to the BinaryLoader. - Loader loader; - if (loadMap.size() > 1) { - loader = loadMap.keySet() - .stream() - .filter(e -> e.getTier() != LoaderTier.UNTARGETED_LOADER) - .findFirst() - .orElse(null); - } - else if (loadMap.size() == 1) { - loader = loadMap.keySet().iterator().next(); - } - else { + // Use the highest priority loader (it will be the first one) + Loader loader = loadMap.keySet().stream().findFirst().orElse(null); + if (loader == null) { return null; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/importer/LoadSpecChooser.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/importer/LoadSpecChooser.java index 00ebb05811..da579c4622 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/importer/LoadSpecChooser.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/importer/LoadSpecChooser.java @@ -15,11 +15,7 @@ */ package ghidra.app.util.importer; -import java.util.Collection; -import java.util.Map; - -import ghidra.app.util.opinion.LoadSpec; -import ghidra.app.util.opinion.Loader; +import ghidra.app.util.opinion.*; /** * Chooses a {@link LoadSpec} for a {@link Loader} to use based on some criteria @@ -30,10 +26,10 @@ public interface LoadSpecChooser { /** * Chooses a {@link LoadSpec} for a {@link Loader} to use based on some criteria * - * @param loadMap A {@link Map} of {@link Loader}s to their respective {@link LoadSpec}s - * @return The chosen {@link LoadSpec} + * @param loadMap A {@link LoadMap} + * @return The chosen {@link LoadSpec}, or null if one could not be found */ - public LoadSpec choose(Map> loadMap); + public LoadSpec choose(LoadMap loadMap); /** * Chooses the first "preferred" {@link LoadSpec} @@ -43,8 +39,8 @@ public interface LoadSpecChooser { public static final LoadSpecChooser CHOOSE_THE_FIRST_PREFERRED = loadMap -> { return loadMap.values() .stream() - .flatMap(e -> e.stream()) - .filter(e -> e != null && e.isPreferred()) + .flatMap(loadSpecs -> loadSpecs.stream()) + .filter(loadSpec -> loadSpec != null && loadSpec.isPreferred()) .findFirst() .orElse(null); }; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/LoadMap.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/LoadMap.java new file mode 100644 index 0000000000..744bf40728 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/LoadMap.java @@ -0,0 +1,44 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.util.opinion; + +import java.util.*; + +/** + * A {@link Map} of {@link Loader}s to their respective {@link LoadSpec}s. + *

+ * The {@link Loader} keys are sorted according to their {@link Loader#compareTo(Loader) natural + * ordering}. + */ +public class LoadMap extends TreeMap> { + + /** + * Creates a new, empty {@link LoadMap} + */ + public LoadMap() { + super((loader1, loader2) -> loader1.compareTo(loader2)); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + for (Loader loader : keySet()) { + Collection loadSpecs = get(loader); + sb.append(loader.getName() + " - " + loadSpecs.size() + " load specs\n"); + } + return sb.toString(); + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/LoaderService.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/LoaderService.java index b97bac4f6d..909b98a7bc 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/LoaderService.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/LoaderService.java @@ -36,13 +36,11 @@ public class LoaderService { * * @param provider The {@link ByteProvider} to load. * @param loaderFilter A {@link Predicate} that will filter out undesired {@link Loader}s. - * @return A {@link Map} of {@link Loader}s to their respective {@link LoadSpec}s. It is safe - * to assume that every {@link Loader} in the {@link Map} will have at least one - * {@link LoadSpec}. + * @return All supported {@link LoadSpec}s in the form of a {@link LoadMap}. */ - public static Map> getSupportedLoadSpecs(ByteProvider provider, + public static LoadMap getSupportedLoadSpecs(ByteProvider provider, Predicate loaderFilter) { - Map> loadMap = new LinkedHashMap<>(); // maintain loader order + LoadMap loadMap = new LoadMap(); for (Loader loader : getAllLoaders()) { if (loaderFilter.test(loader)) { try { @@ -67,27 +65,25 @@ public class LoaderService { * Gets all supported {@link LoadSpec}s for loading the given {@link ByteProvider}. * * @param provider The {@link ByteProvider} to load. - * @return A {@link Map} of {@link Loader}s to their respective {@link LoadSpec}s. It is safe - * to assume that every {@link Loader} in the {@link Map} will have at least one - * {@link LoadSpec}. + * @return All supported {@link LoadSpec}s in the form of a {@link LoadMap}. */ - public static Map> getAllSupportedLoadSpecs( - ByteProvider provider) { + public static LoadMap getAllSupportedLoadSpecs(ByteProvider provider) { return getSupportedLoadSpecs(provider, ACCEPT_ALL); } /** * Gets all known {@link Loader}s' names. * - * @return All known {@link Loader}s' names. + * @return All known {@link Loader}s' names. The {@link Loader} names are sorted + * according to their corresponding {@link Loader}s {@link Loader#compareTo(Loader) natural + * ordering}. */ public static Collection getAllLoaderNames() { - //@formatter:off return getAllLoaders() .stream() + .sorted() .map(loader -> loader.getName()) .collect(Collectors.toList()); - //@formatter:on } /** @@ -99,20 +95,19 @@ public class LoaderService { * name. */ public static Class getLoaderClassByName(String name) { - //@formatter:off return getAllLoaders() .stream() .filter(loader -> loader.getClass().getSimpleName().equals(name)) .findFirst() .map(loader -> loader.getClass()) .orElse(null); - //@formatter:on } /** * Gets an instance of every known {@link Loader}. * - * @return An instance of every known {@link Loader}. + * @return An instance of every known {@link Loader}. The {@link Loader} instances are sorted + * according to their {@link Loader#compareTo(Loader) natural ordering}. */ private synchronized static Collection getAllLoaders() { List loaders = new ArrayList<>(ClassSearcher.getInstances(Loader.class)); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/plugin/importer/AddToProgramDialog.java b/Ghidra/Features/Base/src/main/java/ghidra/plugin/importer/AddToProgramDialog.java index 8dfb90681f..bee50b5c1f 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/plugin/importer/AddToProgramDialog.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/plugin/importer/AddToProgramDialog.java @@ -15,12 +15,11 @@ */ package ghidra.plugin.importer; -import java.util.*; +import java.util.List; import ghidra.app.util.Option; import ghidra.app.util.bin.ByteProvider; -import ghidra.app.util.opinion.LoadSpec; -import ghidra.app.util.opinion.Loader; +import ghidra.app.util.opinion.*; import ghidra.formats.gfilesystem.FSRL; import ghidra.framework.model.DomainFile; import ghidra.framework.model.DomainFolder; @@ -47,9 +46,8 @@ public class AddToProgramDialog extends ImporterDialog { * @param byteProvider the ByteProvider from which the bytes from the source can be read. * @param addToProgram the program to which the newly imported data will be added */ - protected AddToProgramDialog(PluginTool tool, FSRL fsrl, - Map> loadMap, ByteProvider byteProvider, - Program addToProgram) { + protected AddToProgramDialog(PluginTool tool, FSRL fsrl, LoadMap loadMap, + ByteProvider byteProvider, Program addToProgram) { super("Add To Program: " + fsrl.getPath(), tool, loadMap, byteProvider, null); this.addToProgram = addToProgram; folderNameTextField.setText(getFolderName(addToProgram)); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/plugin/importer/ImporterDialog.java b/Ghidra/Features/Base/src/main/java/ghidra/plugin/importer/ImporterDialog.java index e2a90c203a..c3aea2b2dc 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/plugin/importer/ImporterDialog.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/plugin/importer/ImporterDialog.java @@ -68,7 +68,7 @@ public class ImporterDialog extends DialogComponentProvider { private ProgramManager programManager; protected FSRL fsrl; protected List