mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-04 10:19:23 +02:00
Merge remote-tracking branch 'origin/GP-4145_dev747368_external_symbol_resolver--SQUASHED'
This commit is contained in:
commit
9c31aa48d8
3 changed files with 427 additions and 235 deletions
|
@ -15,21 +15,16 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.app.plugin.core.analysis;
|
package ghidra.app.plugin.core.analysis;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import ghidra.app.services.*;
|
import ghidra.app.services.*;
|
||||||
import ghidra.app.util.importer.MessageLog;
|
import ghidra.app.util.importer.MessageLog;
|
||||||
import ghidra.app.util.opinion.*;
|
import ghidra.app.util.opinion.ElfLoader;
|
||||||
import ghidra.framework.model.*;
|
import ghidra.app.util.opinion.MachoLoader;
|
||||||
import ghidra.framework.options.Options;
|
import ghidra.framework.options.Options;
|
||||||
import ghidra.program.model.address.AddressSetView;
|
import ghidra.program.model.address.AddressSetView;
|
||||||
import ghidra.program.model.listing.Library;
|
|
||||||
import ghidra.program.model.listing.Program;
|
import ghidra.program.model.listing.Program;
|
||||||
import ghidra.program.util.ExternalSymbolResolver;
|
import ghidra.program.util.ExternalSymbolResolver;
|
||||||
|
import ghidra.util.Msg;
|
||||||
import ghidra.util.exception.CancelledException;
|
import ghidra.util.exception.CancelledException;
|
||||||
import ghidra.util.exception.VersionException;
|
|
||||||
import ghidra.util.task.TaskMonitor;
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -61,7 +56,7 @@ public class ExternalSymbolResolverAnalyzer extends AbstractAnalyzer {
|
||||||
if (program.getDomainFile().getParent() == null) {
|
if (program.getDomainFile().getParent() == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Options options = program.getOptions(Program.PROGRAM_INFO);
|
Options options = program.getOptions(Program.PROGRAM_INFO);
|
||||||
String format = options.getString("Executable Format", null);
|
String format = options.getString("Executable Format", null);
|
||||||
return ElfLoader.ELF_NAME.equals(format) || MachoLoader.MACH_O_NAME.equals(format);
|
return ElfLoader.ELF_NAME.equals(format) || MachoLoader.MACH_O_NAME.equals(format);
|
||||||
|
@ -71,65 +66,16 @@ public class ExternalSymbolResolverAnalyzer extends AbstractAnalyzer {
|
||||||
public boolean added(Program program, AddressSetView set, TaskMonitor monitor, MessageLog log)
|
public boolean added(Program program, AddressSetView set, TaskMonitor monitor, MessageLog log)
|
||||||
throws CancelledException {
|
throws CancelledException {
|
||||||
|
|
||||||
Object consumer = new Object();
|
try (ExternalSymbolResolver esr = new ExternalSymbolResolver(
|
||||||
log = new MessageLog(); // For now, we don't want the analysis log spammed
|
program.getDomainFile().getParent().getProjectData(), monitor)) {
|
||||||
ProjectData projectData = program.getDomainFile().getParent().getProjectData();
|
esr.addProgramToFixup(program);
|
||||||
List<Loaded<Program>> loadedPrograms = new ArrayList<>();
|
esr.fixUnresolvedExternalSymbols();
|
||||||
|
esr.logInfo(s -> Msg.info(this, s), false);
|
||||||
// Add program to list
|
if (esr.hasProblemLibraries()) {
|
||||||
loadedPrograms.add(new Loaded<>(program, program.getName(),
|
// causes a popup message at end of analysis session
|
||||||
program.getDomainFile().getParent().getPathname()));
|
esr.logInfo(log::appendMsg, true);
|
||||||
|
|
||||||
// Add external libraries to list
|
|
||||||
for (Library extLibrary : ExternalSymbolResolver.getLibrarySearchList(program)) {
|
|
||||||
monitor.checkCancelled();
|
|
||||||
String libPath = extLibrary.getAssociatedProgramPath();
|
|
||||||
if (libPath == null) {
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DomainFile libDomainFile = projectData.getFile(libPath);
|
|
||||||
if (libDomainFile == null) {
|
|
||||||
log.appendMsg("Referenced external program not found: " + libPath);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
DomainObject libDomainObject =
|
|
||||||
libDomainFile.getDomainObject(consumer, false, false, monitor);
|
|
||||||
if (libDomainObject instanceof Program p) {
|
|
||||||
loadedPrograms.add(new Loaded<>(p, libDomainFile.getName(),
|
|
||||||
libDomainFile.getParent().getPathname()));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
libDomainObject.release(consumer);
|
|
||||||
log.appendMsg("Referenced external program is not a program: " + libPath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (IOException e) {
|
|
||||||
log.appendMsg("Failed to open library dependency project file: " +
|
|
||||||
libDomainFile.getPathname());
|
|
||||||
}
|
|
||||||
catch (VersionException e) {
|
|
||||||
log.appendMsg(
|
|
||||||
"Referenced external program requires updgrade, unable to consider symbols: " +
|
|
||||||
libPath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resolve symbols
|
|
||||||
try {
|
|
||||||
ExternalSymbolResolver.fixUnresolvedExternalSymbols(loadedPrograms, false, log,
|
|
||||||
monitor);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch (IOException e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
for (int i = 1; i < loadedPrograms.size(); i++) {
|
|
||||||
loadedPrograms.get(i).release(consumer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -158,8 +158,16 @@ public class ElfLoader extends AbstractLibrarySupportLoader {
|
||||||
throws CancelledException, IOException {
|
throws CancelledException, IOException {
|
||||||
super.postLoadProgramFixups(loadedPrograms, project, options, messageLog, monitor);
|
super.postLoadProgramFixups(loadedPrograms, project, options, messageLog, monitor);
|
||||||
|
|
||||||
ExternalSymbolResolver.fixUnresolvedExternalSymbols(loadedPrograms, true, messageLog,
|
try (ExternalSymbolResolver esr =
|
||||||
monitor);
|
new ExternalSymbolResolver(project.getProjectData(), monitor)) {
|
||||||
|
for (Loaded<Program> loadedProgram : loadedPrograms) {
|
||||||
|
esr.addProgramToFixup(
|
||||||
|
loadedProgram.getProjectFolderPath() + loadedProgram.getName(),
|
||||||
|
loadedProgram.getDomainObject());
|
||||||
|
}
|
||||||
|
esr.fixUnresolvedExternalSymbols();
|
||||||
|
esr.logInfo(messageLog::appendMsg, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -15,13 +15,13 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.program.util;
|
package ghidra.program.util;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.stream.Collectors;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import db.Transaction;
|
import db.Transaction;
|
||||||
import ghidra.app.util.importer.MessageLog;
|
import ghidra.framework.model.*;
|
||||||
import ghidra.app.util.opinion.Loaded;
|
|
||||||
import ghidra.framework.options.Options;
|
import ghidra.framework.options.Options;
|
||||||
import ghidra.program.model.listing.*;
|
import ghidra.program.model.listing.*;
|
||||||
import ghidra.program.model.symbol.*;
|
import ghidra.program.model.symbol.*;
|
||||||
|
@ -30,8 +30,15 @@ import ghidra.util.StringUtilities;
|
||||||
import ghidra.util.exception.*;
|
import ghidra.util.exception.*;
|
||||||
import ghidra.util.task.TaskMonitor;
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
|
||||||
public class ExternalSymbolResolver {
|
/**
|
||||||
|
* Moves dangling external function symbols found in the {@link Library#UNKNOWN EXTERNAL/UNKNOWN}
|
||||||
|
* namespace into the namespace of the external library that publishes a matching symbol.
|
||||||
|
* <p>
|
||||||
|
* This uses an ordered list of external library names that was attached to the program during
|
||||||
|
* import by the Elf or Macho loader (see {@link #REQUIRED_LIBRARY_PROPERTY_PREFIX}).
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class ExternalSymbolResolver implements Closeable {
|
||||||
private final static String REQUIRED_LIBRARY_PROPERTY_PREFIX = "Required Library [";
|
private final static String REQUIRED_LIBRARY_PROPERTY_PREFIX = "Required Library [";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -45,127 +52,415 @@ public class ExternalSymbolResolver {
|
||||||
StringUtilities.pad("" + libraryIndex, ' ', 4));
|
StringUtilities.pad("" + libraryIndex, ' ', 4));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private final ProjectData projectData;
|
||||||
|
private final TaskMonitor monitor;
|
||||||
|
private final List<ProgramSymbolResolver> programsToFix = new ArrayList<>();
|
||||||
|
private final Map<String, Program> loadedPrograms = new HashMap<>();
|
||||||
|
private final Map<String, Throwable> problemLibraries = new HashMap<>();
|
||||||
|
|
||||||
|
public ExternalSymbolResolver(ProjectData projectData, TaskMonitor monitor) {
|
||||||
|
this.projectData = projectData;
|
||||||
|
this.monitor = monitor;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Links unresolved symbols to the first symbol found in the (ordered) linked
|
* Queues a program into this session that will be fixed when {@link #fixUnresolvedExternalSymbols()}
|
||||||
* libraries (saved in the program's properties as {@value #REQUIRED_LIBRARY_PROPERTY_PREFIX}).
|
* is called.
|
||||||
* <p>
|
* <p>
|
||||||
* The ordering and precedence logic is loader specific though no particular binary formats
|
* The program should be fully persisted to the project if using this method, otherwise use
|
||||||
* are parsed or required.
|
* {@link #addProgramToFixup(String, Program)} and specify the pathname the program will
|
||||||
* <p>
|
* be saved to.
|
||||||
* The program's external libraries need to already be populated with paths to
|
*
|
||||||
* already existing / imported libraries.
|
* @param program {@link Program} to fix
|
||||||
*
|
|
||||||
* @param loadedPrograms The {@link Loaded} {@link Program}s to fix. The first entry is the
|
|
||||||
* "primary" {@link Loaded} {@link Program}.
|
|
||||||
* @param fixAll True if all of the {@link Loaded} {@link Program}s should be fixed;
|
|
||||||
* false if just the "primary" {@link Loaded} {@link Program} should be fixed.
|
|
||||||
* @param messageLog {@link MessageLog} to write info message to.
|
|
||||||
* @param monitor {@link TaskMonitor} to watch for cancel and update with progress.
|
|
||||||
* @throws CancelledException if user cancels
|
|
||||||
* @throws IOException if error reading
|
|
||||||
*/
|
*/
|
||||||
public static void fixUnresolvedExternalSymbols(List<Loaded<Program>> loadedPrograms,
|
public void addProgramToFixup(Program program) {
|
||||||
boolean fixAll, MessageLog messageLog, TaskMonitor monitor)
|
addProgramToFixup(program.getDomainFile().getPathname(), program);
|
||||||
throws CancelledException, IOException {
|
}
|
||||||
Map<String, Loaded<Program>> loadedByPath = loadedPrograms.stream()
|
|
||||||
.collect(Collectors.toMap(
|
|
||||||
loaded -> loaded.getProjectFolderPath() + loaded.getName(), loaded -> loaded));
|
|
||||||
|
|
||||||
List<Loaded<Program>> fixupList =
|
/**
|
||||||
loadedPrograms.subList(0, fixAll ? loadedPrograms.size() : 1);
|
* Queues a program into this session that will be fixed when {@link #fixUnresolvedExternalSymbols()}
|
||||||
|
* is called.
|
||||||
|
*
|
||||||
|
* @param programPath string project path to the program
|
||||||
|
* @param program {@link Program} to fix
|
||||||
|
*/
|
||||||
|
public void addProgramToFixup(String programPath, Program program) {
|
||||||
|
programsToFix.add(new ProgramSymbolResolver(program, programPath));
|
||||||
|
addLoadedProgram(programPath, program);
|
||||||
|
}
|
||||||
|
|
||||||
monitor.initialize(fixupList.size());
|
/**
|
||||||
for (Loaded<Program> loadedProgram : fixupList) {
|
* Adds an already opened program to this session, allowing it to be used as an external
|
||||||
Program program = loadedProgram.getDomainObject();
|
* library without needing to look it up in the current project.
|
||||||
|
*
|
||||||
|
* @param programPath project path to already opened program
|
||||||
|
* @param program {@link Program}
|
||||||
|
*/
|
||||||
|
public void addLoadedProgram(String programPath, Program program) {
|
||||||
|
if (loadedPrograms.put(programPath, program) == null) {
|
||||||
|
program.addConsumer(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Collection<Long> unresolvedExternalFunctionIds =
|
/**
|
||||||
getUnresolvedExternalFunctionIds(program);
|
* Returns true if there was an error encountered when trying to open an external library.
|
||||||
if (unresolvedExternalFunctionIds.size() == 0) {
|
*
|
||||||
continue;
|
* @return boolean flag, true if there was a problem opening an external library
|
||||||
|
*/
|
||||||
|
public boolean hasProblemLibraries() {
|
||||||
|
return !problemLibraries.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
for (Program prog : loadedPrograms.values()) {
|
||||||
|
prog.release(this);
|
||||||
|
}
|
||||||
|
programsToFix.clear();
|
||||||
|
loadedPrograms.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves any unresolved external symbols in each program that has been queued up via
|
||||||
|
* {@link #addProgramToFixup(String, Program)}.
|
||||||
|
*
|
||||||
|
* @throws CancelledException if cancelled
|
||||||
|
*/
|
||||||
|
public void fixUnresolvedExternalSymbols() throws CancelledException {
|
||||||
|
for (ProgramSymbolResolver psr : programsToFix) {
|
||||||
|
psr.resolveExternalSymbols();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs information about the libraries and symbols that were found during the fixup.
|
||||||
|
*
|
||||||
|
* @param logger consumer that will log a string
|
||||||
|
* @param shortSummary boolean flag, if true individual symbol names will be omitted
|
||||||
|
*/
|
||||||
|
public void logInfo(Consumer<String> logger, boolean shortSummary) {
|
||||||
|
for (ProgramSymbolResolver psr : programsToFix) {
|
||||||
|
psr.log(logger, shortSummary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches a program from a cache of Program instances. If the requested program
|
||||||
|
* isn't currently in the cache, it will be opened (if possible).
|
||||||
|
* <p>
|
||||||
|
* This cache of programs are pinned by registering a consumer on the program, and will be
|
||||||
|
* released during {@link #close()} of this ExternalSymbolServer instance.
|
||||||
|
* <p>
|
||||||
|
* This cache is shared between all ProgramSymbolResolver instances (that were created
|
||||||
|
* by calling {@link #addProgramToFixup(String, Program)}).
|
||||||
|
*
|
||||||
|
* @param libPath project path to a library program
|
||||||
|
* @return {@link Program}, or null if not found or other error during opening
|
||||||
|
* @throws CancelledException if cancelled
|
||||||
|
*/
|
||||||
|
protected Program getLibraryProgram(String libPath) throws CancelledException {
|
||||||
|
Program result = loadedPrograms.get(libPath);
|
||||||
|
if (result == null && !problemLibraries.containsKey(libPath)) {
|
||||||
|
result = openLibraryFile(projectData.getFile(libPath), libPath);
|
||||||
|
|
||||||
|
if (result != null) {
|
||||||
|
loadedPrograms.put(libPath, result);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
List<Library> libSearchList = getLibrarySearchList(program);
|
/**
|
||||||
if (libSearchList.isEmpty()) {
|
* Opens a library binary.
|
||||||
continue;
|
*
|
||||||
|
* @param libDf optional, reference to a the DomainFile that was found in a project. If null
|
||||||
|
* (meaning a lookup in the project failed to find a matching file), libPath will be used when
|
||||||
|
* creating error strings that reference the problematic file
|
||||||
|
* @param libPath project path for the DomainFile
|
||||||
|
* @return a opened {@link Program}
|
||||||
|
* @throws CancelledException if cancelled
|
||||||
|
*/
|
||||||
|
protected Program openLibraryFile(DomainFile libDf, String libPath) throws CancelledException {
|
||||||
|
try {
|
||||||
|
if (libDf == null) {
|
||||||
|
throw new IOException("Dangling external path: " + libPath);
|
||||||
}
|
}
|
||||||
|
DomainObject libDo = libDf.getDomainObject(this, false, false, monitor);
|
||||||
|
if (libDo instanceof Program p) {
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
libDo.release(this);
|
||||||
|
throw new IOException("Referenced external program is not a program: " + libPath);
|
||||||
|
}
|
||||||
|
catch (IOException | VersionException e) {
|
||||||
|
problemLibraries.put(libPath, e);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
try (Transaction tx = program.openTransaction("Resolve External Symbols")) {
|
/**
|
||||||
|
* Represents a program that needs its external symbols to be fixed.
|
||||||
messageLog.appendMsg("----- [" + program.getName() + "] Resolve " +
|
*/
|
||||||
unresolvedExternalFunctionIds.size() + " external symbols -----");
|
private class ProgramSymbolResolver {
|
||||||
|
record ExtLibInfo(String name, Library lib, String programPath, Program program,
|
||||||
for (Library extLibrary : libSearchList) {
|
List<String> resolvedSymbols, Throwable problem) {
|
||||||
monitor.checkCancelled();
|
String getProblemMessage() {
|
||||||
String libName = extLibrary.getName();
|
if (problem instanceof VersionException ve) {
|
||||||
String libPath = extLibrary.getAssociatedProgramPath();
|
return getVersionError(ve);
|
||||||
if (libPath == null) {
|
}
|
||||||
continue;
|
return problem != null ? problem.getMessage() : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
Loaded<Program> loadedLib = loadedByPath.get(libPath);
|
String getLibPath() {
|
||||||
if (loadedLib == null) {
|
return programPath != null ? programPath : "missing";
|
||||||
messageLog.appendMsg("Referenced external program not found: " + libName);
|
}
|
||||||
continue;
|
|
||||||
}
|
String getVersionError(VersionException ve) {
|
||||||
|
String versionType = switch (ve.getVersionIndicator()) {
|
||||||
Program libProgram = loadedLib.getDomainObject();
|
case VersionException.NEWER_VERSION -> " newer";
|
||||||
monitor.setMessage("Resolving symbols published by library " + libName);
|
case VersionException.OLDER_VERSION -> "n older";
|
||||||
resolveSymbolsToLibrary(program, unresolvedExternalFunctionIds, extLibrary,
|
default -> "n unknown";
|
||||||
libProgram, messageLog, monitor);
|
};
|
||||||
|
|
||||||
|
String upgradeMsg = ve.isUpgradable() ? " (upgrade is possible)" : "";
|
||||||
|
|
||||||
|
return "skipped: file was created with a%s version of Ghidra%s"
|
||||||
|
.formatted(versionType, upgradeMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Program program;
|
||||||
|
String programPath;
|
||||||
|
int externalSymbolCount;
|
||||||
|
List<Long> unresolvedExternalFunctionIds;
|
||||||
|
List<ExtLibInfo> extLibs = new ArrayList<>();
|
||||||
|
|
||||||
|
private ProgramSymbolResolver(Program program, String programPath) {
|
||||||
|
this.program = program;
|
||||||
|
this.programPath = programPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getResolvedSymbolCount() {
|
||||||
|
return externalSymbolCount - unresolvedExternalFunctionIds.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void log(Consumer<String> logger, boolean shortSummary) {
|
||||||
|
boolean changed = unresolvedExternalFunctionIds.size() != externalSymbolCount;
|
||||||
|
if (extLibs.isEmpty() && externalSymbolCount == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (!changed && !hasSomeLibrariesConfigured()) {
|
||||||
|
logger.accept(
|
||||||
|
"Resolving External Symbols of [%s] - %d unresolved symbols, no external libraries configured - skipping"
|
||||||
|
.formatted(programPath, externalSymbolCount));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.accept("Resolving External Symbols of [%s]%s".formatted(programPath,
|
||||||
|
shortSummary ? " - Summary" : ""));
|
||||||
|
logger.accept("\t%d external symbols resolved, %d remain unresolved"
|
||||||
|
.formatted(getResolvedSymbolCount(), unresolvedExternalFunctionIds.size()));
|
||||||
|
for (ExtLibInfo extLib : extLibs) {
|
||||||
|
if (extLib.problem != null) {
|
||||||
|
logger.accept("\t[%s] -> %s, %s".formatted(extLib.name, extLib.getLibPath(),
|
||||||
|
extLib.getProblemMessage()));
|
||||||
|
}
|
||||||
|
else if (extLib.programPath != null) {
|
||||||
|
logger.accept("\t[%s] -> %s, %d new symbols resolved".formatted(extLib.name,
|
||||||
|
extLib.getLibPath(), extLib.resolvedSymbols.size()));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
logger.accept("\t[%s] -> %s".formatted(extLib.name, extLib.getLibPath()));
|
||||||
|
}
|
||||||
|
if (!shortSummary) {
|
||||||
|
for (String symbolName : extLib.resolvedSymbols) {
|
||||||
|
logger.accept("\t\t[%s]".formatted(symbolName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!shortSummary && changed) {
|
||||||
|
if (!unresolvedExternalFunctionIds.isEmpty()) {
|
||||||
|
logger.accept("\tUnresolved remaining %d:"
|
||||||
|
.formatted(unresolvedExternalFunctionIds.size()));
|
||||||
|
SymbolTable symbolTable = program.getSymbolTable();
|
||||||
|
for (Long symId : unresolvedExternalFunctionIds) {
|
||||||
|
Symbol s = symbolTable.getSymbol(symId);
|
||||||
|
logger.accept("\t\t[%s]".formatted(s.getName()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
messageLog.appendMsg("Unresolved external symbols which remain: " +
|
|
||||||
unresolvedExternalFunctionIds.size());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean hasSomeLibrariesConfigured() {
|
||||||
|
for (ExtLibInfo extLib : extLibs) {
|
||||||
|
if (extLib.program != null || extLib.problem != null ||
|
||||||
|
extLib.programPath != null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void resolveExternalSymbols() throws CancelledException {
|
||||||
|
unresolvedExternalFunctionIds = getUnresolvedExternalFunctionIds();
|
||||||
|
externalSymbolCount = unresolvedExternalFunctionIds.size();
|
||||||
|
|
||||||
|
if (unresolvedExternalFunctionIds.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
extLibs = getLibsToSearch();
|
||||||
|
|
||||||
|
if (!extLibs.isEmpty()) {
|
||||||
|
try (Transaction tx = program.openTransaction("Resolve External Symbols")) {
|
||||||
|
for (ExtLibInfo extLib : extLibs) {
|
||||||
|
monitor.checkCancelled();
|
||||||
|
resolveSymbolsToLibrary(extLib);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an ordered list of external libraries that need to be searched.
|
||||||
|
*
|
||||||
|
* @return list of ExtLibInfo elements, each representing an external library dependency
|
||||||
|
* found in the {@link #program}
|
||||||
|
* @throws CancelledException if cancelled
|
||||||
|
*/
|
||||||
|
private List<ExtLibInfo> getLibsToSearch() throws CancelledException {
|
||||||
|
List<ExtLibInfo> result = new ArrayList<>();
|
||||||
|
ExternalManager externalManager = program.getExternalManager();
|
||||||
|
for (String libName : getOrderedRequiredLibraryNames()) {
|
||||||
|
Library lib = externalManager.getExternalLibrary(libName);
|
||||||
|
String libPath = lib != null ? lib.getAssociatedProgramPath() : null;
|
||||||
|
Program libProg = libPath != null ? getLibraryProgram(libPath) : null;
|
||||||
|
Throwable problem =
|
||||||
|
libProg == null && libPath != null ? problemLibraries.get(libPath) : null;
|
||||||
|
|
||||||
|
result.add(
|
||||||
|
new ExtLibInfo(libName, lib, libPath, libProg, new ArrayList<>(), problem));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Moves unresolved functions from the EXTERNAL/UNKNOWN namespace to the namespace of the
|
||||||
|
* external library if the extLib publishes a symbol with a matching name.
|
||||||
|
*
|
||||||
|
* @param extLib {@link ExtLibInfo} representing an external library
|
||||||
|
* @throws CancelledException if cancelled
|
||||||
|
*/
|
||||||
|
private void resolveSymbolsToLibrary(ExtLibInfo extLib) throws CancelledException {
|
||||||
|
if (extLib.program == null) {
|
||||||
|
// can't do anything if the external library doesn't have a valid program associated
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ExternalManager externalManager = program.getExternalManager();
|
||||||
|
SymbolTable symbolTable = program.getSymbolTable();
|
||||||
|
|
||||||
|
for (Iterator<Long> idIterator = unresolvedExternalFunctionIds.iterator(); idIterator
|
||||||
|
.hasNext();) {
|
||||||
|
monitor.checkCancelled();
|
||||||
|
Symbol s = symbolTable.getSymbol(idIterator.next());
|
||||||
|
if (s == null || !s.isExternal() || s.getSymbolType() != SymbolType.FUNCTION) {
|
||||||
|
Msg.error(ExternalSymbolResolver.class,
|
||||||
|
"Concurrent modification of symbol table while resolving external symbols");
|
||||||
|
idIterator.remove();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ExternalLocation extLoc = externalManager.getExternalLocation(s);
|
||||||
|
String extLocName =
|
||||||
|
Objects.requireNonNullElse(extLoc.getOriginalImportedName(), extLoc.getLabel());
|
||||||
|
if (isExportedSymbol(extLib.program, extLocName)) {
|
||||||
|
try {
|
||||||
|
s.setNamespace(extLib.lib);
|
||||||
|
idIterator.remove();
|
||||||
|
extLib.resolvedSymbols.add(s.getName());
|
||||||
|
}
|
||||||
|
catch (DuplicateNameException | InvalidInputException
|
||||||
|
| CircularDependencyException e) {
|
||||||
|
Msg.error(ExternalSymbolResolver.class,
|
||||||
|
"Error setting external symbol namespace for " + extLoc.getLabel(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of all external functions under the EXTERNAL/UNKNOWN library.
|
||||||
|
*
|
||||||
|
* @return list of func ids that need to be fixed
|
||||||
|
*/
|
||||||
|
private List<Long> getUnresolvedExternalFunctionIds() {
|
||||||
|
List<Long> symbolIds = new ArrayList<>();
|
||||||
|
ExternalManager externalManager = program.getExternalManager();
|
||||||
|
Library library = externalManager.getExternalLibrary(Library.UNKNOWN);
|
||||||
|
if (library != null) {
|
||||||
|
for (Symbol s : program.getSymbolTable().getSymbols(library)) {
|
||||||
|
if (s.getSymbolType() == SymbolType.FUNCTION &&
|
||||||
|
s.getSource() != SourceType.DEFAULT) {
|
||||||
|
symbolIds.add(s.getID());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return symbolIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an ordered list of library names, as specified by the logic/rules of the original
|
||||||
|
* operating system's loader (eg. Elf / MachO dynamic library loading / symbol resolving
|
||||||
|
* rules)
|
||||||
|
*
|
||||||
|
* @return list of library names, in original order
|
||||||
|
*/
|
||||||
|
private Collection<String> getOrderedRequiredLibraryNames() {
|
||||||
|
TreeMap<Integer, String> orderLibraryMap = new TreeMap<>();
|
||||||
|
Options options = program.getOptions(Program.PROGRAM_INFO);
|
||||||
|
for (String optionName : options.getOptionNames()) {
|
||||||
|
|
||||||
|
// Legacy programs may have the old "ELF Required Library [" program property, so
|
||||||
|
// we should not assume that the option name starts exactly with
|
||||||
|
// REQUIRED_LIBRARY_PROPERTY_PREFIX. We must deal with a potential substring at the
|
||||||
|
// start of the option name.
|
||||||
|
int prefixIndex = optionName.indexOf(REQUIRED_LIBRARY_PROPERTY_PREFIX);
|
||||||
|
if (prefixIndex == -1 || !optionName.endsWith("]")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String libName = options.getString(optionName, null);
|
||||||
|
if (libName == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String indexStr = optionName
|
||||||
|
.substring(prefixIndex + REQUIRED_LIBRARY_PROPERTY_PREFIX.length(),
|
||||||
|
optionName.length() - 1)
|
||||||
|
.trim();
|
||||||
|
try {
|
||||||
|
orderLibraryMap.put(Integer.parseInt(indexStr), libName.trim());
|
||||||
|
}
|
||||||
|
catch (NumberFormatException e) {
|
||||||
|
Msg.error(ExternalSymbolResolver.class,
|
||||||
|
"Program contains invalid property: " + optionName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return orderLibraryMap.values();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void resolveSymbolsToLibrary(Program program,
|
/**
|
||||||
Collection<Long> unresolvedExternalFunctionIds, Library extLibrary, Program libProgram,
|
* Returns true if the specified program publishes a symbol with the specified name.
|
||||||
MessageLog messageLog, TaskMonitor monitor) throws CancelledException {
|
*
|
||||||
int libResolvedCount = 0;
|
* @param program {@link Program}
|
||||||
ExternalManager externalManager = program.getExternalManager();
|
* @param name symbol name
|
||||||
SymbolTable symbolTable = program.getSymbolTable();
|
* @return true if program publishes a symbol the specified name
|
||||||
|
*/
|
||||||
|
private static boolean isExportedSymbol(Program program, String name) {
|
||||||
|
|
||||||
Iterator<Long> idIterator = unresolvedExternalFunctionIds.iterator();
|
for (Symbol s : program.getSymbolTable().getLabelOrFunctionSymbols(name, null)) {
|
||||||
while (idIterator.hasNext()) {
|
|
||||||
monitor.checkCancelled();
|
|
||||||
Symbol s = symbolTable.getSymbol(idIterator.next());
|
|
||||||
if (s == null || !s.isExternal() || s.getSymbolType() != SymbolType.FUNCTION) {
|
|
||||||
Msg.error(ExternalSymbolResolver.class,
|
|
||||||
"Concurrent modification of symbol table while resolving external symbols");
|
|
||||||
idIterator.remove();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
ExternalLocation extLoc = externalManager.getExternalLocation(s);
|
|
||||||
if (s.getSource() == SourceType.DEFAULT ||
|
|
||||||
!isLocationContainedInLibrary(libProgram, extLoc)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
s.setNamespace(extLibrary);
|
|
||||||
idIterator.remove();
|
|
||||||
libResolvedCount++;
|
|
||||||
Msg.debug(ExternalSymbolResolver.class, "External symbol " + extLoc.getLabel() +
|
|
||||||
" resolved to " + extLibrary.getName());
|
|
||||||
}
|
|
||||||
catch (DuplicateNameException | InvalidInputException | CircularDependencyException e) {
|
|
||||||
Msg.error(ExternalSymbolResolver.class,
|
|
||||||
"Error setting external symbol namespace for " + extLoc.getLabel(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
messageLog.appendMsg(
|
|
||||||
"Resolved " + libResolvedCount + " symbols to library " + extLibrary.getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean isLocationContainedInLibrary(Program libProgram,
|
|
||||||
ExternalLocation extLoc) {
|
|
||||||
|
|
||||||
String name = extLoc.getOriginalImportedName();
|
|
||||||
if (name == null) {
|
|
||||||
name = extLoc.getLabel();
|
|
||||||
}
|
|
||||||
for (Symbol s : libProgram.getSymbolTable().getLabelOrFunctionSymbols(name, null)) {
|
|
||||||
if (s.isExternalEntryPoint()) {
|
if (s.isExternalEntryPoint()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -173,61 +468,4 @@ public class ExternalSymbolResolver {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Collection<Long> getUnresolvedExternalFunctionIds(Program program) {
|
|
||||||
List<Long> symbolIds = new ArrayList<>();
|
|
||||||
ExternalManager externalManager = program.getExternalManager();
|
|
||||||
Library library = externalManager.getExternalLibrary(Library.UNKNOWN);
|
|
||||||
if (library != null) {
|
|
||||||
for (Symbol s : program.getSymbolTable().getSymbols(library)) {
|
|
||||||
if (s.getSymbolType() == SymbolType.FUNCTION) {
|
|
||||||
symbolIds.add(s.getID());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return symbolIds;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Collection<String> getOrderedLibraryNamesNeeded(Program program) {
|
|
||||||
TreeMap<Integer, String> orderLibraryMap = new TreeMap<>();
|
|
||||||
Options options = program.getOptions(Program.PROGRAM_INFO);
|
|
||||||
for (String optionName : options.getOptionNames()) {
|
|
||||||
|
|
||||||
// Legacy programs may have the old "ELF Required Library [" program property, so
|
|
||||||
// we should not assume that the option name starts exactly with
|
|
||||||
// REQUIRED_LIBRARY_PROPERTY_PREFIX. We must deal with a potential substring at the
|
|
||||||
// start of the option name.
|
|
||||||
int prefixIndex = optionName.indexOf(REQUIRED_LIBRARY_PROPERTY_PREFIX);
|
|
||||||
if (prefixIndex == -1 || !optionName.endsWith("]")) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
String libName = options.getString(optionName, null);
|
|
||||||
if (libName == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
String indexStr = optionName
|
|
||||||
.substring(prefixIndex + REQUIRED_LIBRARY_PROPERTY_PREFIX.length(),
|
|
||||||
optionName.length() - 1)
|
|
||||||
.trim();
|
|
||||||
try {
|
|
||||||
orderLibraryMap.put(Integer.parseInt(indexStr), libName.trim());
|
|
||||||
}
|
|
||||||
catch (NumberFormatException e) {
|
|
||||||
Msg.error(ExternalSymbolResolver.class,
|
|
||||||
"Program contains invalid property: " + optionName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return orderLibraryMap.values();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static List<Library> getLibrarySearchList(Program program) {
|
|
||||||
List<Library> result = new ArrayList<>();
|
|
||||||
ExternalManager externalManager = program.getExternalManager();
|
|
||||||
for (String libName : getOrderedLibraryNamesNeeded(program)) {
|
|
||||||
Library lib = externalManager.getExternalLibrary(libName);
|
|
||||||
if (lib != null) {
|
|
||||||
result.add(lib);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue