GP-5429: Reexport fix for Mach-O object files where header is not at

imagebase
This commit is contained in:
Ryan Kurtz 2025-03-21 12:48:14 -04:00
parent 69a66e0eec
commit 1345dbb192
6 changed files with 63 additions and 46 deletions

View file

@ -159,7 +159,7 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
*/
@Override
protected void postLoadProgramFixups(List<Loaded<Program>> loadedPrograms, Project project,
LoadSpec loadSpec, List<Option> options, MessageLog messageLog, TaskMonitor monitor)
LoadSpec loadSpec, List<Option> options, MessageLog log, TaskMonitor monitor)
throws CancelledException, IOException {
if (loadedPrograms.isEmpty() ||
(!isLinkExistingLibraries(options) && !isLoadLibraries(options))) {
@ -167,10 +167,10 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
}
List<DomainFolder> searchFolders =
getLibrarySearchFolders(loadedPrograms, project, options);
getLibrarySearchFolders(loadedPrograms, project, options, log);
List<LibrarySearchPath> searchPaths = getLibrarySearchPaths(
loadedPrograms.getFirst().getDomainObject(), loadSpec, options, messageLog, monitor);
loadedPrograms.getFirst().getDomainObject(), loadSpec, options, log, monitor);
List<Loaded<Program>> saveablePrograms =
loadedPrograms.stream().filter(Predicate.not(Loaded::shouldDiscard)).toList();
@ -191,7 +191,7 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
int id = program.startTransaction("Resolving external references");
try {
resolveExternalLibraries(program, saveablePrograms, searchFolders, searchPaths,
options, monitor, messageLog);
options, monitor, log);
}
finally {
program.endTransaction(id, true);
@ -286,12 +286,13 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
* @param projectFolderPath The project folder path the program will get saved to. Could be null
* if the program is not getting saved to the project.
* @param options a {@link List} of {@link Option}s
* @param log The log
* @return The path of the project folder to search for existing libraries, or null if no
* project folders can be or should be searched
*/
protected DomainFolder getLinkSearchFolder(Project project, Program program,
String projectFolderPath, List<Option> options) {
if (!shouldSearchAllPaths(program, options) && !isLinkExistingLibraries(options)) {
String projectFolderPath, List<Option> options, MessageLog log) {
if (!shouldSearchAllPaths(program, options, log) && !isLinkExistingLibraries(options)) {
return null;
}
if (project == null) {
@ -401,18 +402,19 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
* @param loadedPrograms the list of {@link Loaded} {@link Program}s
* @param project The {@link Project} to load into. Could be null if there is no project.
* @param options The {@link List} of {@link Option}s
* @param log The log
* @return A {@link List} of library search {@link DomainFolder folders} based on the current
* options
*/
protected List<DomainFolder> getLibrarySearchFolders(List<Loaded<Program>> loadedPrograms,
Project project, List<Option> options) {
Project project, List<Option> options, MessageLog log) {
List<DomainFolder> searchFolders = new ArrayList<>();
String projectFolderPath = loadedPrograms.get(0).getProjectFolderPath();
String destPath = getLibraryDestinationFolderPath(project, projectFolderPath, options);
DomainFolder destSearchFolder =
getLibraryDestinationSearchFolder(project, destPath, options);
DomainFolder linkSearchFolder = getLinkSearchFolder(project,
loadedPrograms.getFirst().getDomainObject(), projectFolderPath, options);
loadedPrograms.getFirst().getDomainObject(), projectFolderPath, options, log);
Optional.ofNullable(destSearchFolder).ifPresent(searchFolders::add);
Optional.ofNullable(linkSearchFolder).ifPresent(searchFolders::add);
return searchFolders;
@ -424,9 +426,10 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
*
* @param program The {@link Program} being loaded
* @param options a {@link List} of {@link Option}s
* @param log The log
* @return True if all possible search paths should be used, regardless of what options are set
*/
protected boolean shouldSearchAllPaths(Program program, List<Option> options) {
protected boolean shouldSearchAllPaths(Program program, List<Option> options, MessageLog log) {
return false;
}
@ -527,7 +530,7 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
List<LibrarySearchPath> searchPaths =
getLibrarySearchPaths(program, desiredLoadSpec, options, log, monitor);
DomainFolder linkSearchFolder =
getLinkSearchFolder(project, program, projectFolderPath, options);
getLinkSearchFolder(project, program, projectFolderPath, options, log);
String libraryDestFolderPath =
getLibraryDestinationFolderPath(project, projectFolderPath, options);
DomainFolder libraryDestFolder =
@ -554,7 +557,7 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
log.appendMsg("Found %s in %s...".formatted(library, linkSearchFolder));
log.appendMsg("------------------------------------------------\n");
}
else if (isLoadLibraries(options) || shouldSearchAllPaths(program, options)) {
else if (isLoadLibraries(options) || shouldSearchAllPaths(program, options, log)) {
Loaded<Program> loadedLibrary = loadLibraryFromSearchPaths(library, provider,
customSearchPaths, libraryDestFolderPath, unprocessed, depth,
desiredLoadSpec, options, log, consumer, monitor);
@ -894,13 +897,12 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
private void resolveExternalLibraries(Program program, List<Loaded<Program>> loadedPrograms,
List<DomainFolder> searchFolders, List<LibrarySearchPath> fsSearchPaths,
List<Option> options, TaskMonitor monitor, MessageLog messageLog)
List<Option> options, TaskMonitor monitor, MessageLog log)
throws CancelledException {
ExternalManager extManager = program.getExternalManager();
String[] extLibNames = extManager.getExternalLibraryNames();
messageLog.appendMsg(
"Linking the External Programs of '%s' to imported libraries..."
.formatted(program.getName()));
log.appendMsg("Linking the External Programs of '%s' to imported libraries..."
.formatted(program.getName()));
for (String externalLibName : extLibNames) {
if (Library.UNKNOWN.equals(externalLibName)) {
continue;
@ -911,7 +913,7 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
if (match != null) {
String path = match.getProjectFolderPath() + match.getName();
extManager.setExternalPath(externalLibName, path, false);
messageLog.appendMsg(" [" + externalLibName + "] -> [" + path + "]");
log.appendMsg(" [" + externalLibName + "] -> [" + path + "]");
}
else {
boolean found = false;
@ -921,14 +923,14 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
if (alreadyImportedLib != null) {
extManager.setExternalPath(externalLibName,
alreadyImportedLib.getPathname(), false);
messageLog.appendMsg(" [" + externalLibName + "] -> [" +
log.appendMsg(" [" + externalLibName + "] -> [" +
alreadyImportedLib.getPathname() + "] (previously imported)");
found = true;
break;
}
}
if (!found) {
messageLog.appendMsg(" [" + externalLibName + "] -> not found in project");
log.appendMsg(" [" + externalLibName + "] -> not found in project");
}
}
}
@ -936,7 +938,7 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
Msg.error(this, "Bad library name: " + externalLibName, e);
}
}
messageLog.appendMsg("------------------------------------------------\n");
log.appendMsg("------------------------------------------------\n");
}
/**
@ -1023,7 +1025,7 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
*/
protected List<LibrarySearchPath> getLibrarySearchPaths(Program program, LoadSpec loadSpec,
List<Option> options, MessageLog log, TaskMonitor monitor) throws CancelledException {
if (!isLoadLibraries(options) && !shouldSearchAllPaths(program, options)) {
if (!isLoadLibraries(options) && !shouldSearchAllPaths(program, options, log)) {
return List.of();
}

View file

@ -71,7 +71,7 @@ public abstract class AbstractOrdinalSupportLoader extends AbstractLibrarySuppor
}
@Override
protected boolean shouldSearchAllPaths(Program program, List<Option> options) {
protected boolean shouldSearchAllPaths(Program program, List<Option> options, MessageLog log) {
return shouldPerformOrdinalLookup(options);
}

View file

@ -41,7 +41,6 @@ import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Program;
import ghidra.program.model.symbol.*;
import ghidra.util.LittleEndianDataConverter;
import ghidra.util.Msg;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
import util.CollectionUtils;
@ -310,20 +309,25 @@ public class MachoLoader extends AbstractLibrarySupportLoader {
* set and the Mach-O actually has {@code LC_REEXPORT_DYLIB} entries.
*/
@Override
protected boolean shouldSearchAllPaths(Program program, List<Option> options) {
if (super.shouldSearchAllPaths(program, options)) {
protected boolean shouldSearchAllPaths(Program program, List<Option> options, MessageLog log) {
if (super.shouldSearchAllPaths(program, options, log)) {
return true;
}
if (shouldPerformReexports(options)) {
try {
ByteProvider provider = new MemoryByteProvider(program.getMemory(),
program.getImageBase());
if (new MachHeader(provider).parseAndCheck(LoadCommandTypes.LC_REEXPORT_DYLIB)) {
Symbol header =
program.getSymbolTable().getSymbols(MachoProgramBuilder.HEADER_SYMBOL).next();
if (header == null) {
return false;
}
ByteProvider p = new MemoryByteProvider(program.getMemory(), header.getAddress());
if (new MachHeader(p).parseAndCheck(LoadCommandTypes.LC_REEXPORT_DYLIB)) {
return true;
}
}
catch (IOException | MachException e) {
Msg.error(this, "Failed to parse Mach-O header for: " + program.getName());
catch (Exception e) {
log.appendMsg("Failed to parse Mach-O header for: '%s': %s"
.formatted(program.getName(), e.getMessage()));
}
}
return false;
@ -348,7 +352,7 @@ public class MachoLoader extends AbstractLibrarySupportLoader {
}
try {
for (String path : getReexportPaths(lib)) {
for (String path : getReexportPaths(lib, log)) {
unprocessed.add(new UnprocessedLibrary(path, depth, depth == 1));
}
}
@ -361,12 +365,21 @@ public class MachoLoader extends AbstractLibrarySupportLoader {
* Gets a {@link List} of reexport library paths from the given {@link Program}
*
* @param program The {@link Program}
* @param log The log
* @return A {@link List} of reexport library paths from the given {@link Program}
* @throws MachException if there was a problem parsing the Mach-O {@link Program}
* @throws IOException if there was an IO-related error
*/
private List<String> getReexportPaths(Program program) throws MachException, IOException {
ByteProvider p = new MemoryByteProvider(program.getMemory(), program.getImageBase());
private List<String> getReexportPaths(Program program, MessageLog log)
throws MachException, IOException {
Symbol header =
program.getSymbolTable().getSymbols(MachoProgramBuilder.HEADER_SYMBOL).next();
if (header == null) {
log.appendMsg("Failed to lookup reexport paths...couldn't find '%s' symbol"
.formatted(MachoProgramBuilder.HEADER_SYMBOL));
return List.of();
}
ByteProvider p = new MemoryByteProvider(program.getMemory(), header.getAddress());
return new MachHeader(p).parseReexports()
.stream()
.map(DynamicLibraryCommand::getDynamicLibrary)
@ -382,17 +395,16 @@ public class MachoLoader extends AbstractLibrarySupportLoader {
*/
@Override
protected void postLoadProgramFixups(List<Loaded<Program>> loadedPrograms, Project project,
LoadSpec loadSpec, List<Option> options, MessageLog messageLog, TaskMonitor monitor)
LoadSpec loadSpec, List<Option> options, MessageLog log, TaskMonitor monitor)
throws CancelledException, IOException {
if (shouldPerformReexports(options)) {
List<DomainFolder> searchFolders =
getLibrarySearchFolders(loadedPrograms, project, options);
getLibrarySearchFolders(loadedPrograms, project, options, log);
List<LibrarySearchPath> searchPaths =
getLibrarySearchPaths(loadedPrograms.getFirst().getDomainObject(), loadSpec,
options, messageLog, monitor);
List<LibrarySearchPath> searchPaths = getLibrarySearchPaths(
loadedPrograms.getFirst().getDomainObject(), loadSpec, options, log, monitor);
monitor.initialize(loadedPrograms.size());
for (Loaded<Program> loadedProgram : loadedPrograms) {
@ -402,10 +414,10 @@ public class MachoLoader extends AbstractLibrarySupportLoader {
int id = program.startTransaction("Reexporting");
try {
reexport(program, loadedPrograms, searchFolders, searchPaths, options, monitor,
messageLog);
log);
}
catch (Exception e) {
messageLog.appendException(e);
log.appendException(e);
}
finally {
program.endTransaction(id, true);
@ -413,7 +425,7 @@ public class MachoLoader extends AbstractLibrarySupportLoader {
}
}
super.postLoadProgramFixups(loadedPrograms, project, loadSpec, options, messageLog,
super.postLoadProgramFixups(loadedPrograms, project, loadSpec, options, log,
monitor);
}
@ -428,16 +440,16 @@ public class MachoLoader extends AbstractLibrarySupportLoader {
* @param searchPaths A {@link List} of file system search paths that will be searched
* @param options The load options
* @param monitor A cancelable task monitor
* @param messageLog The log
* @param log The log
* @throws CancelledException if the user cancelled the load operation
* @throws IOException if there was an IO-related error during the load
*/
private void reexport(Program program, List<Loaded<Program>> loadedPrograms,
List<DomainFolder> searchFolders, List<LibrarySearchPath> searchPaths,
List<Option> options, TaskMonitor monitor, MessageLog messageLog)
List<Option> options, TaskMonitor monitor, MessageLog log)
throws CancelledException, Exception {
for (String path : getReexportPaths(program)) {
for (String path : getReexportPaths(program, log)) {
monitor.checkCancelled();
Program programToRelease = null;
try {
@ -469,7 +481,7 @@ public class MachoLoader extends AbstractLibrarySupportLoader {
.filter(Objects::nonNull)
.toList();
Address addr = MachoProgramUtils.addExternalBlock(program,
reexportedSymbols.size() * 8, messageLog);
reexportedSymbols.size() * 8, log);
monitor.initialize(reexportedSymbols.size(), "Reexporting symbols...");
for (Symbol symbol : reexportedSymbols) {
monitor.increment();

View file

@ -66,6 +66,8 @@ import ghidra.util.task.TaskMonitor;
*/
public class MachoProgramBuilder {
public static final String HEADER_SYMBOL = "MACH_HEADER";
protected MachHeader machoHeader;
protected Program program;
protected ByteProvider provider;
@ -975,6 +977,7 @@ public class MachoProgramBuilder {
try {
DataUtilities.createData(program, headerAddr, header.toDataType(), -1,
DataUtilities.ClearDataMode.CHECK_FOR_SPACE);
program.getSymbolTable().createLabel(headerAddr, HEADER_SYMBOL, SourceType.IMPORTED);
monitor.initialize(header.getLoadCommands().size(), "Marking up header...");
for (LoadCommand loadCommand : header.getLoadCommands()) {

View file

@ -169,7 +169,7 @@ public class DyldCacheExtractLoader extends MachoLoader {
}
@Override
protected boolean shouldSearchAllPaths(Program program, List<Option> options) {
protected boolean shouldSearchAllPaths(Program program, List<Option> options, MessageLog log) {
return false;
}

View file

@ -102,7 +102,7 @@ public class MachoFileSetExtractLoader extends MachoLoader {
}
@Override
protected boolean shouldSearchAllPaths(Program program, List<Option> options) {
protected boolean shouldSearchAllPaths(Program program, List<Option> options, MessageLog log) {
return false;
}