GP-5429: Support for Mach-O LC_REEXPORT_DYLIB

This commit is contained in:
Ryan Kurtz 2025-02-27 13:49:43 -05:00
parent 136a944796
commit 17910774cd
10 changed files with 514 additions and 189 deletions

View file

@ -217,6 +217,30 @@ public class MachHeader implements StructConverter {
return segments; return segments;
} }
/**
* Parses only this {@link MachHeader}'s {@link DynamicLibraryCommand reexport load commands}
*
* @return A {@link List} of this {@link MachHeader}'s
* {@link DynamicLibraryCommand reexport load commands}
* @throws IOException If there was an IO-related error
*/
public List<DynamicLibraryCommand> parseReexports() throws IOException {
List<DynamicLibraryCommand> cmds = new ArrayList<>();
_reader.setPointerIndex(_commandIndex);
for (int i = 0; i < nCmds; ++i) {
int type = _reader.peekNextInt();
if (type == LoadCommandTypes.LC_REEXPORT_DYLIB) {
DynamicLibraryCommand cmd = new DynamicLibraryCommand(_reader);
cmds.add(cmd);
_reader.setPointerIndex(cmd.getStartIndex());
}
type = _reader.readNextInt();
long size = _reader.readNextUnsignedInt();
_reader.setPointerIndex(_reader.getPointerIndex() + size - 8);
}
return cmds;
}
/** /**
* Parses only this {@link MachHeader}'s {@link LoadCommand}s to check to see if one of the * Parses only this {@link MachHeader}'s {@link LoadCommand}s to check to see if one of the
* given type exists * given type exists

View file

@ -34,7 +34,7 @@ import ghidra.util.task.TaskMonitor;
public class DynamicLibraryCommand extends LoadCommand { public class DynamicLibraryCommand extends LoadCommand {
private DynamicLibrary dylib; private DynamicLibrary dylib;
DynamicLibraryCommand(BinaryReader reader) throws IOException { public DynamicLibraryCommand(BinaryReader reader) throws IOException {
super(reader); super(reader);
dylib = new DynamicLibrary(reader, this); dylib = new DynamicLibrary(reader, this);
} }

View file

@ -122,6 +122,7 @@ public class ExportTrie {
if ((flags & EXPORT_SYMBOL_FLAGS_REEXPORT) != 0) { if ((flags & EXPORT_SYMBOL_FLAGS_REEXPORT) != 0) {
ulebOffsets.add(reader.getPointerIndex() - base); ulebOffsets.add(reader.getPointerIndex() - base);
other = reader.readNext(LEB128::unsigned); // dylib ordinal other = reader.readNext(LEB128::unsigned); // dylib ordinal
stringOffsets.add(reader.getPointerIndex() - base);
importName = reader.readNextAsciiString(); importName = reader.readNextAsciiString();
} }
else { else {

View file

@ -20,6 +20,7 @@ import java.io.IOException;
import java.nio.file.InvalidPathException; import java.nio.file.InvalidPathException;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.*; import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
@ -154,28 +155,47 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
log.appendMsg("--------------------------------------------------------------------\n"); log.appendMsg("--------------------------------------------------------------------\n");
} }
/**
* {@inheritDoc}
* <p>
* Fix up program's external library entries so that they point to a path in the project.
*/
@Override @Override
protected void postLoadProgramFixups(List<Loaded<Program>> loadedPrograms, Project project, protected void postLoadProgramFixups(List<Loaded<Program>> loadedPrograms, Project project,
List<Option> options, MessageLog messageLog, TaskMonitor monitor) List<Option> options, MessageLog messageLog, TaskMonitor monitor)
throws CancelledException, IOException { throws CancelledException, IOException {
if (loadedPrograms.isEmpty()) { if (loadedPrograms.isEmpty() ||
(!isLinkExistingLibraries(options) && !isLoadLibraries(options))) {
return; return;
} }
if (isLinkExistingLibraries(options) || isLoadLibraries(options)) {
String projectFolderPath = loadedPrograms.get(0).getProjectFolderPath(); List<DomainFolder> searchFolders =
List<DomainFolder> searchFolders = new ArrayList<>(); getLibrarySearchFolders(loadedPrograms, project, options);
String destPath = getLibraryDestinationFolderPath(project, projectFolderPath, options);
DomainFolder destSearchFolder = List<Loaded<Program>> saveablePrograms =
getLibraryDestinationSearchFolder(project, destPath, options); loadedPrograms.stream().filter(Predicate.not(Loaded::shouldDiscard)).toList();
DomainFolder linkSearchFolder =
getLinkSearchFolder(project, projectFolderPath, options); monitor.initialize(saveablePrograms.size());
if (destSearchFolder != null) { for (Loaded<Program> loadedProgram : saveablePrograms) {
searchFolders.add(destSearchFolder); monitor.increment();
Program program = loadedProgram.getDomainObject();
ExternalManager extManager = program.getExternalManager();
String[] extLibNames = extManager.getExternalLibraryNames();
if (extLibNames.length == 0 ||
(extLibNames.length == 1 && Library.UNKNOWN.equals(extLibNames[0]))) {
continue; // skip program if no libraries defined
} }
if (linkSearchFolder != null) {
searchFolders.add(linkSearchFolder); monitor.setMessage("Resolving..." + program.getName());
int id = program.startTransaction("Resolving external references");
try {
resolveExternalLibraries(program, saveablePrograms, searchFolders, monitor,
messageLog);
}
finally {
program.endTransaction(id, true);
} }
fixupExternalLibraries(loadedPrograms, searchFolders, messageLog, monitor);
} }
} }
@ -262,15 +282,16 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
* Gets the {@link DomainFolder project folder} to search for existing libraries * Gets the {@link DomainFolder project folder} to search for existing libraries
* *
* @param project The {@link Project}. Could be null if there is no project. * @param project The {@link Project}. Could be null if there is no project.
* @param program The {@link Program} being loaded
* @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
* @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, String projectFolderPath, protected DomainFolder getLinkSearchFolder(Project project, Program program,
List<Option> options) { String projectFolderPath, List<Option> options) {
if (!shouldSearchAllPaths(options) && !isLinkExistingLibraries(options)) { if (!shouldSearchAllPaths(program, options) && !isLinkExistingLibraries(options)) {
return null; return null;
} }
if (project == null) { if (project == null) {
@ -373,14 +394,39 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
return project.getProjectData().getFolder(libraryDestinationFolderPath); return project.getProjectData().getFolder(libraryDestinationFolderPath);
} }
/**
* Gets a {@link List} of library search {@link DomainFolder folders} based on the current
* options
*
* @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
* @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) {
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);
Optional.ofNullable(destSearchFolder).ifPresent(searchFolders::add);
Optional.ofNullable(linkSearchFolder).ifPresent(searchFolders::add);
return searchFolders;
}
/** /**
* Checks whether or not to search for libraries using all possible search paths, regardless * Checks whether or not to search for libraries using all possible search paths, regardless
* of what options are set * of what options are set
* *
* @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
* @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(List<Option> options) { protected boolean shouldSearchAllPaths(Program program, List<Option> options) {
return false; return false;
} }
@ -433,6 +479,8 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
* @param libraryName The name of the library * @param libraryName The name of the library
* @param libraryFsrl The library {@link FSRL} * @param libraryFsrl The library {@link FSRL}
* @param provider The library bytes * @param provider The library bytes
* @param unprocessed The {@link Queue} of {@link UnprocessedLibrary unprocessed libraries}
* @param depth The load depth of the library to load
* @param loadSpec The {@link LoadSpec} used for the load * @param loadSpec The {@link LoadSpec} used for the load
* @param options The options * @param options The options
* @param log The log * @param log The log
@ -441,8 +489,9 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
* @throws CancelledException If the user cancelled the action * @throws CancelledException If the user cancelled the action
*/ */
protected void processLibrary(Program library, String libraryName, FSRL libraryFsrl, protected void processLibrary(Program library, String libraryName, FSRL libraryFsrl,
ByteProvider provider, LoadSpec loadSpec, List<Option> options, MessageLog log, ByteProvider provider, Queue<UnprocessedLibrary> unprocessed, int depth,
TaskMonitor monitor) throws IOException, CancelledException { LoadSpec loadSpec, List<Option> options, MessageLog log, TaskMonitor monitor)
throws IOException, CancelledException {
// Default behavior is to do nothing // Default behavior is to do nothing
} }
@ -478,7 +527,8 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
getCustomLibrarySearchPaths(provider, options, log, monitor); getCustomLibrarySearchPaths(provider, options, log, monitor);
List<FileSystemSearchPath> searchPaths = List<FileSystemSearchPath> searchPaths =
getLibrarySearchPaths(provider, program, options, log, monitor); getLibrarySearchPaths(provider, program, options, log, monitor);
DomainFolder linkSearchFolder = getLinkSearchFolder(project, projectFolderPath, options); DomainFolder linkSearchFolder =
getLinkSearchFolder(project, program, projectFolderPath, options);
String libraryDestFolderPath = String libraryDestFolderPath =
getLibraryDestinationFolderPath(project, projectFolderPath, options); getLibraryDestinationFolderPath(project, projectFolderPath, options);
DomainFolder libraryDestFolder = DomainFolder libraryDestFolder =
@ -490,6 +540,7 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
monitor.checkCancelled(); monitor.checkCancelled();
UnprocessedLibrary unprocessedLibrary = unprocessed.remove(); UnprocessedLibrary unprocessedLibrary = unprocessed.remove();
String libraryName = unprocessedLibrary.name(); String libraryName = unprocessedLibrary.name();
boolean discard = unprocessedLibrary.discard();
int depth = unprocessedLibrary.depth(); int depth = unprocessedLibrary.depth();
if (depth == 0 || processed.contains(libraryName)) { if (depth == 0 || processed.contains(libraryName)) {
continue; continue;
@ -519,7 +570,8 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
provider, customSearchPaths, libraryDestFolderPath, unprocessed, depth, provider, customSearchPaths, libraryDestFolderPath, unprocessed, depth,
desiredLoadSpec, options, log, consumer, monitor); desiredLoadSpec, options, log, consumer, monitor);
if (loadedLibrary != null) { if (loadedLibrary != null) {
loaded = true; loaded = loadLibraries && !discard;
loadedLibrary.setDiscard(!loaded);
loadedPrograms.add(loadedLibrary); loadedPrograms.add(loadedLibrary);
} }
} }
@ -530,23 +582,17 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
provider, searchPaths, libraryDestFolderPath, unprocessed, depth, provider, searchPaths, libraryDestFolderPath, unprocessed, depth,
desiredLoadSpec, options, log, consumer, monitor); desiredLoadSpec, options, log, consumer, monitor);
if (loadedLibrary != null) { if (loadedLibrary != null) {
if (loadLibraries) { loaded = loadLibraries && !discard;
loaded = true; loadedLibrary.setDiscard(!loaded);
loadedPrograms.add(loadedLibrary); loadedPrograms.add(loadedLibrary);
}
else {
loadedLibrary.release(consumer);
}
} }
} }
if (loaded) {
log.appendMsg("Saving library to: " +
loadedPrograms.get(loadedPrograms.size() - 1).toString());
}
else { else {
if (loaded) { log.appendMsg("Library not saved to project.");
log.appendMsg("Saving library to: " +
loadedPrograms.get(loadedPrograms.size() - 1).toString());
}
else {
log.appendMsg("Library not saved to project.");
}
} }
log.appendMsg("------------------------------------------------\n"); log.appendMsg("------------------------------------------------\n");
} }
@ -624,13 +670,13 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
library = loadLibrary(simpleLibraryName, candidateLibraryFsrl, library = loadLibrary(simpleLibraryName, candidateLibraryFsrl,
desiredLoadSpec, newLibraryList, options, consumer, log, monitor); desiredLoadSpec, newLibraryList, options, consumer, log, monitor);
for (String newLibraryName : newLibraryList) { for (String newLibraryName : newLibraryList) {
unprocessed.add(new UnprocessedLibrary(newLibraryName, depth - 1)); unprocessed.add(new UnprocessedLibrary(newLibraryName, depth - 1, false));
} }
if (library == null) { if (library == null) {
continue; continue;
} }
processLibrary(library, libraryName, candidateLibraryFsrl, provider, processLibrary(library, libraryName, candidateLibraryFsrl, provider, unprocessed,
desiredLoadSpec, options, log, monitor); depth, desiredLoadSpec, options, log, monitor);
success = true; success = true;
return new Loaded<Program>(library, simpleLibraryName, libraryDestFolderPath); return new Loaded<Program>(library, simpleLibraryName, libraryDestFolderPath);
} }
@ -664,7 +710,7 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
* If null this method will return null. * If null this method will return null.
* @return The found {@link DomainFile} or null if not found * @return The found {@link DomainFile} or null if not found
*/ */
private DomainFile findLibrary(String libraryPath, DomainFolder folder) { protected DomainFile findLibrary(String libraryPath, DomainFolder folder) {
if (folder == null) { if (folder == null) {
return null; return null;
} }
@ -897,66 +943,6 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
return libraryNames; return libraryNames;
} }
/**
* For each {@link Loaded} {@link Program} in the given list, fix up its external library
* entries so that they point to a path in the project.
* <p>
* Other {@link Program}s in the given list are matched first, then the given
* {@link DomainFolder search folder} is searched for matches.
*
* @param loadedPrograms the list of {@link Loaded} {@link Program}s
* @param searchFolders an ordered list of {@link DomainFolder}s which imported libraries will
* be searched. These folders will be searched if a library is not found within the list of
* programs supplied.
* @param messageLog log for messages.
* @param monitor the task monitor
* @throws IOException if there was an IO-related problem resolving.
* @throws CancelledException if the user cancelled the load.
*/
private void fixupExternalLibraries(List<Loaded<Program>> loadedPrograms,
List<DomainFolder> searchFolders, MessageLog messageLog, TaskMonitor monitor)
throws CancelledException, IOException {
monitor.initialize(loadedPrograms.size());
for (Loaded<Program> loadedProgram : loadedPrograms) {
monitor.increment();
Program program = loadedProgram.getDomainObject();
ExternalManager extManager = program.getExternalManager();
String[] extLibNames = extManager.getExternalLibraryNames();
if (extLibNames.length == 0 ||
(extLibNames.length == 1 && Library.UNKNOWN.equals(extLibNames[0]))) {
continue; // skip program if no libraries defined
}
monitor.setMessage("Resolving..." + program.getName());
int id = program.startTransaction("Resolving external references");
try {
resolveExternalLibraries(program, loadedPrograms, searchFolders, monitor,
messageLog);
}
finally {
program.endTransaction(id, true);
}
}
}
/**
* Fix up program's external library entries so that they point to a path in the project.
* <p>
* Other programs in the map are matched first, then the ghidraLibSearchFolders
* are searched for matches.
*
* @param program the program whose Library entries are to be resolved. An open
* transaction on program is required.
* @param loadedPrograms the list of {@link Loaded} {@link Program}s
* @param searchFolders an order list of {@link DomainFolder}s which imported libraries will be
* searched. These folders will be searched if a library is not found within the list of
* programs supplied.
* @param messageLog log for messages.
* @param monitor the task monitor
* @throws CancelledException if the user cancelled the load.
*/
private void resolveExternalLibraries(Program program, private void resolveExternalLibraries(Program program,
List<Loaded<Program>> loadedPrograms, List<DomainFolder> searchFolders, List<Loaded<Program>> loadedPrograms, List<DomainFolder> searchFolders,
TaskMonitor monitor, MessageLog messageLog) throws CancelledException { TaskMonitor monitor, MessageLog messageLog) throws CancelledException {
@ -1009,8 +995,9 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
* @param name The name of the library * @param name The name of the library
* @param depth The recursive load depth of the library (based on the original binary being * @param depth The recursive load depth of the library (based on the original binary being
* loaded) * loaded)
* @param discard True if the library should be discarded (not saved) after processing
*/ */
private record UnprocessedLibrary(String name, int depth) {/**/} protected record UnprocessedLibrary(String name, int depth, boolean discard) {/**/}
/** /**
* Creates a new {@link Queue} of {@link UnprocessedLibrary}s, initialized filled with the * Creates a new {@link Queue} of {@link UnprocessedLibrary}s, initialized filled with the
@ -1022,7 +1009,7 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
*/ */
private Queue<UnprocessedLibrary> createUnprocessedQueue(List<String> libraryNames, int depth) { private Queue<UnprocessedLibrary> createUnprocessedQueue(List<String> libraryNames, int depth) {
return libraryNames.stream() return libraryNames.stream()
.map(name -> new UnprocessedLibrary(name, depth)) .map(name -> new UnprocessedLibrary(name, depth, false))
.collect(Collectors.toCollection(LinkedList::new)); .collect(Collectors.toCollection(LinkedList::new));
} }
@ -1067,7 +1054,7 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
*/ */
private List<FileSystemSearchPath> getLibrarySearchPaths(ByteProvider provider, Program program, private List<FileSystemSearchPath> getLibrarySearchPaths(ByteProvider provider, Program program,
List<Option> options, MessageLog log, TaskMonitor monitor) throws CancelledException { List<Option> options, MessageLog log, TaskMonitor monitor) throws CancelledException {
if (!isLoadLibraries(options) && !shouldSearchAllPaths(options)) { if (!isLoadLibraries(options) && !shouldSearchAllPaths(program, options)) {
return List.of(); return List.of();
} }
@ -1119,7 +1106,8 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
* be a simple filename or an absolute path. * be a simple filename or an absolute path.
* @return The found {@link Loaded} {@link Program} or null if not found * @return The found {@link Loaded} {@link Program} or null if not found
*/ */
private Loaded<Program> findLibrary(List<Loaded<Program>> loadedPrograms, String libraryName) { protected Loaded<Program> findLibrary(List<Loaded<Program>> loadedPrograms,
String libraryName) {
Comparator<String> comparator = getLibraryNameComparator(); Comparator<String> comparator = getLibraryNameComparator();
boolean noExtension = FilenameUtils.getExtension(libraryName).equals(""); boolean noExtension = FilenameUtils.getExtension(libraryName).equals("");
boolean absolute = libraryName.startsWith("/"); boolean absolute = libraryName.startsWith("/");

View file

@ -17,8 +17,8 @@ package ghidra.app.util.opinion;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.Iterator; import java.util.*;
import java.util.List; import java.util.function.Predicate;
import generic.jar.ResourceFile; import generic.jar.ResourceFile;
import ghidra.app.util.Option; import ghidra.app.util.Option;
@ -71,13 +71,14 @@ public abstract class AbstractOrdinalSupportLoader extends AbstractLibrarySuppor
} }
@Override @Override
protected boolean shouldSearchAllPaths(List<Option> options) { protected boolean shouldSearchAllPaths(Program program, List<Option> options) {
return shouldPerformOrdinalLookup(options); return shouldPerformOrdinalLookup(options);
} }
@Override @Override
protected void processLibrary(Program lib, String libName, FSRL libFsrl, ByteProvider provider, protected void processLibrary(Program lib, String libName, FSRL libFsrl, ByteProvider provider,
LoadSpec loadSpec, List<Option> options, MessageLog log, TaskMonitor monitor) Queue<UnprocessedLibrary> unprocessed, int depth, LoadSpec loadSpec,
List<Option> options, MessageLog log, TaskMonitor monitor)
throws IOException, CancelledException { throws IOException, CancelledException {
int size = loadSpec.getLanguageCompilerSpec().getLanguageDescription().getSize(); int size = loadSpec.getLanguageCompilerSpec().getLanguageDescription().getSize();
ResourceFile existingExportsFile = LibraryLookupTable.getExistingExportsFile(libName, size); ResourceFile existingExportsFile = LibraryLookupTable.getExistingExportsFile(libName, size);
@ -128,10 +129,12 @@ public abstract class AbstractOrdinalSupportLoader extends AbstractLibrarySuppor
protected void postLoadProgramFixups(List<Loaded<Program>> loadedPrograms, Project project, protected void postLoadProgramFixups(List<Loaded<Program>> loadedPrograms, Project project,
List<Option> options, MessageLog messageLog, TaskMonitor monitor) List<Option> options, MessageLog messageLog, TaskMonitor monitor)
throws CancelledException, IOException { throws CancelledException, IOException {
monitor.initialize(loadedPrograms.size());
if (shouldPerformOrdinalLookup(options)) { if (shouldPerformOrdinalLookup(options)) {
for (Loaded<Program> loadedProgram : loadedPrograms) { List<Loaded<Program>> saveablePrograms =
loadedPrograms.stream().filter(Predicate.not(Loaded::shouldDiscard)).toList();
monitor.initialize(saveablePrograms.size());
for (Loaded<Program> loadedProgram : saveablePrograms) {
monitor.checkCancelled(); monitor.checkCancelled();
Program program = loadedProgram.getDomainObject(); Program program = loadedProgram.getDomainObject();
int id = program.startTransaction("Ordinal fixups"); int id = program.startTransaction("Ordinal fixups");

View file

@ -17,8 +17,7 @@ package ghidra.app.util.opinion;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.ArrayList; import java.util.*;
import java.util.List;
import ghidra.app.plugin.processors.generic.MemoryBlockDefinition; import ghidra.app.plugin.processors.generic.MemoryBlockDefinition;
import ghidra.app.util.Option; import ghidra.app.util.Option;
@ -141,6 +140,16 @@ public abstract class AbstractProgramLoader implements Loader {
// Subclasses can perform custom post-load fix-ups // Subclasses can perform custom post-load fix-ups
postLoadProgramFixups(loadedPrograms, project, options, messageLog, monitor); postLoadProgramFixups(loadedPrograms, project, options, messageLog, monitor);
// Discard unneeded programs
Iterator<Loaded<Program>> iter = loadedPrograms.iterator();
while (iter.hasNext()) {
Loaded<Program> loaded = iter.next();
if (loaded.shouldDiscard()) {
iter.remove();
loaded.release(consumer);
}
}
success = true; success = true;
return new LoadResults<Program>(loadedPrograms); return new LoadResults<Program>(loadedPrograms);
} }
@ -513,7 +522,7 @@ public abstract class AbstractProgramLoader implements Loader {
Namespace namespace = program.getGlobalNamespace(); Namespace namespace = program.getGlobalNamespace();
s = symTable.createLabel(addr, labelname, namespace, SourceType.IMPORTED); s = symTable.createLabel(addr, labelname, namespace, SourceType.IMPORTED);
if (comment != null) { if (comment != null) {
program.getListing().setComment(address, CodeUnit.EOL_COMMENT, comment); program.getListing().setComment(address, CommentType.EOL, comment);
} }
if (isEntry) { if (isEntry) {
symTable.addExternalEntryPoint(addr); symTable.addExternalEntryPoint(addr);

View file

@ -28,6 +28,9 @@ import ghidra.util.task.TaskMonitor;
* A loaded {@link DomainObject} produced by a {@link Loader}. In addition to storing the loaded * A loaded {@link DomainObject} produced by a {@link Loader}. In addition to storing the loaded
* {@link DomainObject}, it also stores the {@link Loader}'s desired name and project folder path * {@link DomainObject}, it also stores the {@link Loader}'s desired name and project folder path
* for the loaded {@link DomainObject}, should it get saved to a project. * for the loaded {@link DomainObject}, should it get saved to a project.
* <p>
* NOTE: If an object of this type is marked as {@link #setDiscard(boolean) discardable}, it should
* be {@link #release(Object) released} and not saved.
* *
* @param <T> The type of {@link DomainObject} that was loaded * @param <T> The type of {@link DomainObject} that was loaded
*/ */
@ -39,6 +42,7 @@ public class Loaded<T extends DomainObject> {
private DomainFile domainFile; private DomainFile domainFile;
private boolean ignoreSave; private boolean ignoreSave;
private boolean discard;
/** /**
* Creates a new {@link Loaded} object * Creates a new {@link Loaded} object
@ -213,6 +217,27 @@ public class Loaded<T extends DomainObject> {
return domainFile; return domainFile;
} }
/**
* Checks to see if this {@link Loaded} {@link DomainObject} should be discarded (not saved)
*
* @return True if this {@link Loaded} {@link DomainObject} should be discarded; otherwise,
* false
*/
public boolean shouldDiscard() {
return discard;
}
/**
* Sets whether or not this {@link Loaded} {@link DomainObject} should be discarded (not saved)
*
* @param discard True if this {@link Loaded} {@link DomainObject} should be discarded;
* otherwise, false
*/
public void setDiscard(boolean discard) {
this.discard = discard;
}
/** /**
* Deletes the loaded {@link DomainObject}'s associated {@link DomainFile} that was * Deletes the loaded {@link DomainObject}'s associated {@link DomainFile} that was
* {@link #save(Project, MessageLog, TaskMonitor) saved}. This method has no effect if it was * {@link #save(Project, MessageLog, TaskMonitor) saved}. This method has no effect if it was

View file

@ -19,22 +19,28 @@ import java.io.IOException;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.*; import java.util.*;
import ghidra.app.util.MemoryBlockUtils; import ghidra.app.util.*;
import ghidra.app.util.Option; import ghidra.app.util.bin.*;
import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.bin.ByteProviderWrapper;
import ghidra.app.util.bin.format.golang.GoConstants; import ghidra.app.util.bin.format.golang.GoConstants;
import ghidra.app.util.bin.format.golang.rtti.GoRttiMapper; import ghidra.app.util.bin.format.golang.rtti.GoRttiMapper;
import ghidra.app.util.bin.format.macho.*; import ghidra.app.util.bin.format.macho.*;
import ghidra.app.util.bin.format.macho.commands.*;
import ghidra.app.util.bin.format.swift.SwiftUtils; import ghidra.app.util.bin.format.swift.SwiftUtils;
import ghidra.app.util.bin.format.ubi.*; import ghidra.app.util.bin.format.ubi.*;
import ghidra.app.util.importer.MessageLog; import ghidra.app.util.importer.MessageLog;
import ghidra.formats.gfilesystem.*; import ghidra.formats.gfilesystem.*;
import ghidra.framework.model.*;
import ghidra.program.database.mem.FileBytes; import ghidra.program.database.mem.FileBytes;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSet;
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.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;
/** /**
* A {@link Loader} for Mach-O files. * A {@link Loader} for Mach-O files.
@ -44,6 +50,9 @@ public class MachoLoader extends AbstractLibrarySupportLoader {
public final static String MACH_O_NAME = "Mac OS X Mach-O"; public final static String MACH_O_NAME = "Mac OS X Mach-O";
private static final long MIN_BYTE_LENGTH = 4; private static final long MIN_BYTE_LENGTH = 4;
public static final String REEXPORT_OPTION_NAME = "Perform Reexports";
static final boolean REEXPORT_OPTION_DEFAULT = true;
@Override @Override
public Collection<LoadSpec> findSupportedLoadSpecs(ByteProvider provider) throws IOException { public Collection<LoadSpec> findSupportedLoadSpecs(ByteProvider provider) throws IOException {
List<LoadSpec> loadSpecs = new ArrayList<>(); List<LoadSpec> loadSpecs = new ArrayList<>();
@ -121,6 +130,34 @@ public class MachoLoader extends AbstractLibrarySupportLoader {
} }
} }
@Override
public List<Option> getDefaultOptions(ByteProvider provider, LoadSpec loadSpec,
DomainObject domainObject, boolean loadIntoProgram) {
List<Option> list =
super.getDefaultOptions(provider, loadSpec, domainObject, loadIntoProgram);
if (!loadIntoProgram) {
list.add(new Option(REEXPORT_OPTION_NAME, REEXPORT_OPTION_DEFAULT,
Boolean.class, Loader.COMMAND_LINE_ARG_PREFIX + "-reexport"));
}
return list;
}
@Override
public String validateOptions(ByteProvider provider, LoadSpec loadSpec, List<Option> options,
Program program) {
if (options != null) {
for (Option option : options) {
String name = option.getName();
if (name.equals(REEXPORT_OPTION_NAME)) {
if (!Boolean.class.isAssignableFrom(option.getValueClass())) {
return "Invalid type for option: " + name + " - " + option.getValueClass();
}
}
}
}
return super.validateOptions(provider, loadSpec, options, program);
}
@Override @Override
public String getName() { public String getName() {
return MACH_O_NAME; return MACH_O_NAME;
@ -217,4 +254,191 @@ public class MachoLoader extends AbstractLibrarySupportLoader {
} }
return null; return null;
} }
/**
* Checks to see if reexports should be performed
*
* @param options a {@link List} of {@link Option}s
* @return True if reexports should be performed; otherwise, false
*/
private boolean shouldPerformReexports(List<Option> options) {
return OptionUtils.getOption(REEXPORT_OPTION_NAME, options, REEXPORT_OPTION_DEFAULT);
}
/**
* {@inheritDoc}
* <p>
* If we aren't loading libraries, we still want to search all paths if the reexport option is
* 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)) {
return true;
}
if (shouldPerformReexports(options)) {
try {
ByteProvider provider = new MemoryByteProvider(program.getMemory(),
program.getImageBase());
if (new MachHeader(provider).parseAndCheck(LoadCommandTypes.LC_REEXPORT_DYLIB)) {
return true;
}
}
catch (IOException | MachException e) {
Msg.error(this, "Failed to parse Mach-O header for: " + program.getName());
}
}
return false;
}
/**
* {@inheritDoc}
* <p>
* The goal here is to add each reexported library to the {@code unprocessed} list at the
* current {@code depth} to be sure they get loaded. However, if the current depth is 1, we
* need to marked them as "discard" so we know not to save them in the end (since their actual
* depth would have prevented their save as a normal library)
*/
@Override
protected void processLibrary(Program lib, String libName, FSRL libFsrl, ByteProvider provider,
Queue<UnprocessedLibrary> unprocessed, int depth, LoadSpec loadSpec,
List<Option> options, MessageLog log, TaskMonitor monitor)
throws IOException, CancelledException {
if (!shouldPerformReexports(options)) {
return;
}
try {
for (String path : getReexportPaths(lib)) {
unprocessed.add(new UnprocessedLibrary(path, depth, depth == 1));
}
}
catch (MachException e) {
throw new IOException(e);
}
}
/**
* Gets a {@link List} of reexport library paths from the given {@link Program}
*
* @param program The {@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 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());
return new MachHeader(p).parseReexports()
.stream()
.map(DynamicLibraryCommand::getDynamicLibrary)
.map(DynamicLibrary::getName)
.map(LoadCommandString::getString)
.toList();
}
/**
* {@inheritDoc}
* <p>
* Adds reexported symbols to each {@link Loaded} {@link Program}.
*/
@Override
protected void postLoadProgramFixups(List<Loaded<Program>> loadedPrograms, Project project,
List<Option> options, MessageLog messageLog, TaskMonitor monitor)
throws CancelledException, IOException {
if (shouldPerformReexports(options)) {
List<DomainFolder> searchFolders =
getLibrarySearchFolders(loadedPrograms, project, options);
monitor.initialize(loadedPrograms.size());
for (Loaded<Program> loadedProgram : loadedPrograms) {
monitor.increment();
Program program = loadedProgram.getDomainObject();
int id = program.startTransaction("Reexporting");
try {
reexport(program, loadedPrograms, searchFolders, monitor, messageLog);
}
catch (Exception e) {
messageLog.appendException(e);
}
finally {
program.endTransaction(id, true);
}
}
}
super.postLoadProgramFixups(loadedPrograms, project, options, messageLog, monitor);
}
/**
* "Reexports" symbols from to a {@link Program}
*
* @param program The {@link Program} to receive the reexports
* @param loadedPrograms A {@link List} of {@link Loaded} {@link Program}s to find get the
* reexportable symbols from
* @param searchFolders A {@link List} of project folders that may contain already-loaded
* {@link Program}s with reexportable symbols
* @param monitor A cancelable task monitor
* @param messageLog 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, TaskMonitor monitor, MessageLog messageLog)
throws CancelledException, Exception {
for (String path : getReexportPaths(program)) {
Program programToRelease = null;
try {
Loaded<Program> match = findLibrary(loadedPrograms, path);
Program lib = null;
if (match != null) {
lib = match.getDomainObject();
}
if (lib == null) {
for (DomainFolder searchFolder : searchFolders) {
DomainFile df = findLibrary(path, searchFolder);
if (df != null) {
DomainObject obj = df.getDomainObject(this, true, true, monitor);
if (obj instanceof Program p) {
lib = p;
programToRelease = p;
}
break;
}
}
}
if (lib == null) {
continue;
}
List<Symbol> reexportedSymbols = CollectionUtils
.asStream(lib.getSymbolTable().getExternalEntryPointIterator())
.map(lib.getSymbolTable()::getPrimarySymbol)
.filter(Objects::nonNull)
.toList();
Address addr = MachoProgramUtils.addExternalBlock(program,
reexportedSymbols.size() * 8, messageLog);
for (Symbol symbol : reexportedSymbols) {
String name = SymbolUtilities.replaceInvalidChars(symbol.getName(), true);
program.getSymbolTable().addExternalEntryPoint(addr);
program.getSymbolTable().createLabel(addr, name, SourceType.IMPORTED);
Function function = program.getFunctionManager()
.createFunction(name, addr, new AddressSet(addr), SourceType.IMPORTED);
ExternalLocation loc = program.getExternalManager()
.addExtLocation(path, name, null, SourceType.IMPORTED);
function.setThunkedFunction(loc.createFunction());
addr = addr.add(8);
}
}
finally {
if (programToRelease != null) {
programToRelease.release(this);
}
}
}
}
} }

View file

@ -66,10 +66,7 @@ import ghidra.util.task.TaskMonitor;
*/ */
public class MachoProgramBuilder { public class MachoProgramBuilder {
public static final String BLOCK_SOURCE_NAME = "Mach-O Loader";
protected MachHeader machoHeader; protected MachHeader machoHeader;
protected Program program; protected Program program;
protected ByteProvider provider; protected ByteProvider provider;
protected FileBytes fileBytes; protected FileBytes fileBytes;
@ -738,40 +735,30 @@ public class MachoProgramBuilder {
if (undefinedSymbols.size() == 0) { if (undefinedSymbols.size() == 0) {
return; return;
} }
Address start = getAddress();
try { try {
MemoryBlock block = memory.createUninitializedBlock(MemoryBlock.EXTERNAL_BLOCK_NAME, Address addr = MachoProgramUtils.addExternalBlock(program,
start, undefinedSymbols.size() * machoHeader.getAddressSize(), false); undefinedSymbols.size() * machoHeader.getAddressSize(), log);
// assume any value in external is writable. for (NList symbol : undefinedSymbols) {
block.setWrite(true); if (monitor.isCancelled()) {
return;
// Mark block as an artificial fabrication }
block.setArtificial(true); try {
String name = SymbolUtilities.replaceInvalidChars(symbol.getString(), true);
block.setSourceName(BLOCK_SOURCE_NAME); if (name != null && name.length() > 0) {
block.setComment( program.getSymbolTable().createLabel(addr, name, SourceType.IMPORTED);
"NOTE: This block is artificial and is used to make relocations work correctly"); program.getExternalManager()
.addExtLocation(Library.UNKNOWN, name, addr, SourceType.IMPORTED);
}
}
catch (Exception e) {
log.appendMsg("Unable to create undefined symbol: " + e.getMessage());
}
addr = addr.add(machoHeader.getAddressSize());
}
} }
catch (Exception e) { catch (Exception e) {
log.appendMsg("Unable to create undefined memory block: " + e.getMessage()); log.appendMsg("Unable to create undefined memory block: " + e.getMessage());
} }
for (NList symbol : undefinedSymbols) {
if (monitor.isCancelled()) {
return;
}
try {
String name = SymbolUtilities.replaceInvalidChars(symbol.getString(), true);
if (name != null && name.length() > 0) {
program.getSymbolTable().createLabel(start, name, SourceType.IMPORTED);
program.getExternalManager()
.addExtLocation(Library.UNKNOWN, name, start, SourceType.IMPORTED);
}
}
catch (Exception e) {
log.appendMsg("Unable to create undefined symbol: " + e.getMessage());
}
start = start.add(machoHeader.getAddressSize());
}
} }
protected void processAbsoluteSymbols() throws Exception { protected void processAbsoluteSymbols() throws Exception {
@ -802,26 +789,26 @@ public class MachoProgramBuilder {
if (absoluteSymbols.size() == 0) { if (absoluteSymbols.size() == 0) {
return; return;
} }
Address start = getAddress(); Address start = MachoProgramUtils.getNextAvailableAddress(program);
try { try {
memory.createUninitializedBlock("ABSOLUTE", start, memory.createUninitializedBlock("ABSOLUTE", start,
absoluteSymbols.size() * machoHeader.getAddressSize(), false); absoluteSymbols.size() * machoHeader.getAddressSize(), false);
for (NList symbol : absoluteSymbols) {
try {
String name = SymbolUtilities.replaceInvalidChars(symbol.getString(), true);
if (name != null && name.length() > 0) {
program.getSymbolTable().createLabel(start, name, SourceType.IMPORTED);
}
}
catch (Exception e) {
log.appendMsg("Unable to create absolute symbol: " + e.getMessage());
}
start = start.add(machoHeader.getAddressSize());
}
} }
catch (Exception e) { catch (Exception e) {
log.appendMsg("Unable to create absolute memory block: " + e.getMessage()); log.appendMsg("Unable to create absolute memory block: " + e.getMessage());
} }
for (NList symbol : absoluteSymbols) {
try {
String name = SymbolUtilities.replaceInvalidChars(symbol.getString(), true);
if (name != null && name.length() > 0) {
program.getSymbolTable().createLabel(start, name, SourceType.IMPORTED);
}
}
catch (Exception e) {
log.appendMsg("Unable to create absolute symbol: " + e.getMessage());
}
start = start.add(machoHeader.getAddressSize());
}
} }
public List<Address> processChainedFixups(List<String> libraryPaths) throws Exception { public List<Address> processChainedFixups(List<String> libraryPaths) throws Exception {
@ -1497,24 +1484,6 @@ public class MachoProgramBuilder {
} }
} }
private Address getAddress() {
Address maxAddress = null;
for (MemoryBlock block : program.getMemory().getBlocks()) {
if (block.isOverlay()) {
continue;
}
if (maxAddress == null || block.getEnd().compareTo(maxAddress) > 0) {
maxAddress = block.getEnd();
}
}
if (maxAddress == null) {
return space.getAddress(0x1000);
}
long maxAddr = maxAddress.getOffset();
long remainder = maxAddr % 0x1000;
return maxAddress.getNewAddress(maxAddr + 0x1000 - remainder);
}
private MemoryBlock getMemoryBlock(Section section) { private MemoryBlock getMemoryBlock(Section section) {
Address blockAddress = space.getAddress(section.getAddress()); Address blockAddress = space.getAddress(section.getAddress());
return memory.getBlock(blockAddress); return memory.getBlock(blockAddress);

View file

@ -0,0 +1,82 @@
/* ###
* 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 ghidra.app.util.importer.MessageLog;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.Memory;
import ghidra.program.model.mem.MemoryBlock;
public class MachoProgramUtils {
/**
* Gets the next available {@link Address} in the {@link Program}
*
* @param program The {@link Program}
* @return The next available {@link Address} in the {@link Program}
*/
public static Address getNextAvailableAddress(Program program) {
Address maxAddress = null;
for (MemoryBlock block : program.getMemory().getBlocks()) {
if (block.isOverlay()) {
continue;
}
if (maxAddress == null || block.getEnd().compareTo(maxAddress) > 0) {
maxAddress = block.getEnd();
}
}
if (maxAddress == null) {
return program.getAddressFactory().getDefaultAddressSpace().getAddress(0x1000);
}
long maxAddr = maxAddress.getOffset();
long remainder = maxAddr % 0x1000;
return maxAddress.getNewAddress(maxAddr + 0x1000 - remainder);
}
/**
* Adds the {@link MemoryBlock#EXTERNAL_BLOCK_NAME EXERNAL block} to memory, or adds to an
* existing one
*
* @param program The {@link Program}
* @param size The desired size of the new EXTERNAL block
* @param log The {@link MessageLog}
* @return The {@link Address} of the new (or new piece) of EXTERNAL block
* @throws Exception if there was an issue creating or adding to the EXTERNAL block
*/
public static Address addExternalBlock(Program program, long size, MessageLog log)
throws Exception {
Memory mem = program.getMemory();
MemoryBlock externalBlock = mem.getBlock(MemoryBlock.EXTERNAL_BLOCK_NAME);
Address ret;
if (externalBlock != null) {
ret = externalBlock.getEnd().add(1);
MemoryBlock newBlock = mem.createBlock(externalBlock, "REEXPORTS", ret, size);
mem.join(externalBlock, newBlock);
//joinedBlock.setName(MemoryBlock.EXTERNAL_BLOCK_NAME);
}
else {
ret = MachoProgramUtils.getNextAvailableAddress(program);
externalBlock =
mem.createUninitializedBlock(MemoryBlock.EXTERNAL_BLOCK_NAME, ret, size, false);
externalBlock.setWrite(true);
externalBlock.setArtificial(true);
externalBlock.setComment(
"NOTE: This block is artificial and is used to make relocations work correctly");
}
return ret;
}
}