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

View file

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

View file

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

View file

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

View file

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