From 9b4488cc068fff5374aa05b571b3c970ffceda72 Mon Sep 17 00:00:00 2001 From: Ryan Kurtz Date: Tue, 2 Jun 2026 12:28:06 -0400 Subject: [PATCH] GP-6773: Supporting "fallback" loaders --- .../java/ghidra/app/util/opinion/Loader.java | 10 ++++ .../app/util/opinion/LoaderService.java | 47 ++++++++++++++----- .../ghidra/app/util/opinion/LoaderTier.java | 16 ++++--- 3 files changed, 56 insertions(+), 17 deletions(-) diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/Loader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/Loader.java index ce3b9a2f79..72421eb159 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/Loader.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/Loader.java @@ -215,6 +215,16 @@ public interface Loader extends ExtensionPoint, Comparable { */ public int getTierPriority(); + /** + * {@return whether or not this is a "fallback" {@link Loader}} + *

+ * Fallback loaders are only considered during the import process if no other non-fallback + * loaders were compatible with the thing being imported. + */ + public default boolean isFallback() { + return false; + } + /** * The preferred file name to use when loading. *

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 e5a05f449b..cfa6a79cdd 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 @@ -23,6 +23,7 @@ import java.util.stream.Collectors; import ghidra.app.util.bin.ByteProvider; import ghidra.util.Msg; import ghidra.util.classfinder.ClassSearcher; +import util.CollectionUtils; /** * Factory and utility methods for working with {@link Loader}s. @@ -41,26 +42,50 @@ public class LoaderService { public static LoaderMap getSupportedLoadSpecs(ByteProvider provider, Predicate loaderFilter) { LoaderMap loaderMap = new LoaderMap(); + List fallback = new ArrayList<>(); for (Loader loader : getAllLoaders()) { if (loaderFilter.test(loader)) { - try { - Collection loadSpecs = loader.findSupportedLoadSpecs(provider); - if (loadSpecs != null && !loadSpecs.isEmpty()) { // shouldn't be null, but protect against rogue loaders - loaderMap.put(loader, loadSpecs); - } + if (!loader.isFallback()) { + tryLoadSpecs(loader, provider, loaderMap); } - catch (IOException e) { - // file not applicable for loader - } - catch (RuntimeException e) { - Msg.error(LoaderService.class, - "Unexpected Loader exception from " + loader.getName(), e); + else { + fallback.add(loader); } } } + + if (loaderMap.size() <= 1) { // BinaryLoader is always there + for (Loader loader : fallback) { + tryLoadSpecs(loader, provider, loaderMap); + } + } + return loaderMap; } + /** + * Attempts to find and register supported {@link LoadSpec}s for the given {@link Loader} + * + * @param loader The {@link Loader} to query + * @param provider The {@link ByteProvider} to load from + * @param loaderMap The {@link LoaderMap} to populate with discovered {@link LoadSpec}s + */ + private static void tryLoadSpecs(Loader loader, ByteProvider provider, LoaderMap loaderMap) { + try { + Collection loadSpecs = loader.findSupportedLoadSpecs(provider); + if (!CollectionUtils.isBlank(loadSpecs)) { + loaderMap.put(loader, loadSpecs); + } + } + catch (IOException e) { + // file not applicable for loader + } + catch (RuntimeException e) { + Msg.error(LoaderService.class, "Unexpected Loader exception from " + loader.getName(), + e); + } + } + /** * Gets all supported {@link LoadSpec}s for loading the given {@link ByteProvider}. * diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/LoaderTier.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/LoaderTier.java index dba85da436..598d108ab2 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/LoaderTier.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/LoaderTier.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -15,9 +15,13 @@ */ package ghidra.app.util.opinion; +/** + * When sorting, {@link Loader}s are first {@link Loader#compareTo(Loader) sorted} by tier, and then + * by {@link Loader#getTierPriority()} to break ties + */ public enum LoaderTier { - SPECIALIZED_TARGET_LOADER, - GENERIC_TARGET_LOADER, - AMBIGUOUS_TARGET_LOADER, - UNTARGETED_LOADER + SPECIALIZED_TARGET_LOADER, + GENERIC_TARGET_LOADER, + AMBIGUOUS_TARGET_LOADER, + UNTARGETED_LOADER }