mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-03 17:59:46 +02:00
GP-5429: Support for Mach-O LC_REEXPORT_DYLIB
This commit is contained in:
parent
136a944796
commit
17910774cd
10 changed files with 514 additions and 189 deletions
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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,16 +582,11 @@ 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);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (loaded) {
|
if (loaded) {
|
||||||
log.appendMsg("Saving library to: " +
|
log.appendMsg("Saving library to: " +
|
||||||
loadedPrograms.get(loadedPrograms.size() - 1).toString());
|
loadedPrograms.get(loadedPrograms.size() - 1).toString());
|
||||||
|
@ -547,7 +594,6 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
|
||||||
else {
|
else {
|
||||||
log.appendMsg("Library not saved to project.");
|
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("/");
|
||||||
|
|
|
@ -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");
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,23 +735,9 @@ 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.
|
|
||||||
block.setWrite(true);
|
|
||||||
|
|
||||||
// Mark block as an artificial fabrication
|
|
||||||
block.setArtificial(true);
|
|
||||||
|
|
||||||
block.setSourceName(BLOCK_SOURCE_NAME);
|
|
||||||
block.setComment(
|
|
||||||
"NOTE: This block is artificial and is used to make relocations work correctly");
|
|
||||||
}
|
|
||||||
catch (Exception e) {
|
|
||||||
log.appendMsg("Unable to create undefined memory block: " + e.getMessage());
|
|
||||||
}
|
|
||||||
for (NList symbol : undefinedSymbols) {
|
for (NList symbol : undefinedSymbols) {
|
||||||
if (monitor.isCancelled()) {
|
if (monitor.isCancelled()) {
|
||||||
return;
|
return;
|
||||||
|
@ -762,15 +745,19 @@ public class MachoProgramBuilder {
|
||||||
try {
|
try {
|
||||||
String name = SymbolUtilities.replaceInvalidChars(symbol.getString(), true);
|
String name = SymbolUtilities.replaceInvalidChars(symbol.getString(), true);
|
||||||
if (name != null && name.length() > 0) {
|
if (name != null && name.length() > 0) {
|
||||||
program.getSymbolTable().createLabel(start, name, SourceType.IMPORTED);
|
program.getSymbolTable().createLabel(addr, name, SourceType.IMPORTED);
|
||||||
program.getExternalManager()
|
program.getExternalManager()
|
||||||
.addExtLocation(Library.UNKNOWN, name, start, SourceType.IMPORTED);
|
.addExtLocation(Library.UNKNOWN, name, addr, SourceType.IMPORTED);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e) {
|
catch (Exception e) {
|
||||||
log.appendMsg("Unable to create undefined symbol: " + e.getMessage());
|
log.appendMsg("Unable to create undefined symbol: " + e.getMessage());
|
||||||
}
|
}
|
||||||
start = start.add(machoHeader.getAddressSize());
|
addr = addr.add(machoHeader.getAddressSize());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
log.appendMsg("Unable to create undefined memory block: " + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -802,14 +789,10 @@ 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);
|
||||||
}
|
|
||||||
catch (Exception e) {
|
|
||||||
log.appendMsg("Unable to create absolute memory block: " + e.getMessage());
|
|
||||||
}
|
|
||||||
for (NList symbol : absoluteSymbols) {
|
for (NList symbol : absoluteSymbols) {
|
||||||
try {
|
try {
|
||||||
String name = SymbolUtilities.replaceInvalidChars(symbol.getString(), true);
|
String name = SymbolUtilities.replaceInvalidChars(symbol.getString(), true);
|
||||||
|
@ -823,6 +806,10 @@ public class MachoProgramBuilder {
|
||||||
start = start.add(machoHeader.getAddressSize());
|
start = start.add(machoHeader.getAddressSize());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
log.appendMsg("Unable to create absolute memory block: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public List<Address> processChainedFixups(List<String> libraryPaths) throws Exception {
|
public List<Address> processChainedFixups(List<String> libraryPaths) throws Exception {
|
||||||
monitor.setMessage("Fixing up chained pointers...");
|
monitor.setMessage("Fixing up chained pointers...");
|
||||||
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue