GP-3728: Lazy loading of local symbols and other refactoring

This commit is contained in:
Ryan Kurtz
2023-08-11 16:31:15 -04:00
parent 689064b590
commit 3d0395a6fd
9 changed files with 123 additions and 59 deletions

View File

@@ -189,6 +189,30 @@ public class MachHeader implements StructConverter {
return this;
}
/**
* Parses only this {@link MachHeader}'s {@link SegmentCommand segments}
*
* @return A {@List} of this {@link MachHeader}'s {@link SegmentCommand segments}
* @throws IOException If there was an IO-related error
* @throws MachException if the load command is invalid
*/
public List<SegmentCommand> parseSegments() throws IOException, MachException {
List<SegmentCommand> segments = new ArrayList<>();
_reader.setPointerIndex(_commandIndex);
for (int i = 0; i < nCmds; ++i) {
int type = _reader.peekNextInt();
if (type == LoadCommandTypes.LC_SEGMENT || type == LoadCommandTypes.LC_SEGMENT_64) {
segments.add(new SegmentCommand(_reader, is32bit()));
}
else {
type = _reader.readNextInt();
int size = _reader.readNextInt();
_reader.setPointerIndex(_reader.getPointerIndex() + size - 8);
}
}
return segments;
}
public int getMagic() {
return magic;
}

View File

@@ -112,6 +112,10 @@ public class SegmentCommand extends LoadCommand {
return vmaddr;
}
public void setVMaddress(long vmaddr) {
this.vmaddr = vmaddr;
}
public long getVMsize() {
return vmsize;
}

View File

@@ -768,7 +768,7 @@ public class DyldCacheHeader implements StructConverter {
}
}
private void parseLocalSymbolsInfo(boolean shouldParse, MessageLog log, TaskMonitor monitor)
public void parseLocalSymbolsInfo(boolean shouldParse, MessageLog log, TaskMonitor monitor)
throws CancelledException {
if (!shouldParse || localSymbolsOffset == 0) {
return;

View File

@@ -115,6 +115,15 @@ public class DyldCacheLocalSymbolsInfo implements StructConverter {
markupNList(program, localSymbolsInfoAddr, monitor, log);
}
/**
* Gets the {@link List} of {@link DyldCacheLocalSymbolsEntry}s.
*
* @return The {@link List} of {@link DyldCacheLocalSymbolsEntry}
*/
public List<DyldCacheLocalSymbolsEntry> getLocalSymbolsEntries() {
return localSymbolsEntryList;
}
/**
* Gets the {@link List} of {@link NList}.
*
@@ -125,12 +134,20 @@ public class DyldCacheLocalSymbolsInfo implements StructConverter {
}
/**
* Gets the {@link List} of {@link DyldCacheLocalSymbolsEntry}s.
* Gets the {@link List} of {@link NList} for the given dylib offset.
*
* @return The {@link List} of {@link DyldCacheLocalSymbolsEntry}
* @param dylibOffset The offset of dylib in the DYLD Cache
* @return The {@link List} of {@link NList} for the given dylib offset
*/
public List<DyldCacheLocalSymbolsEntry> getLocalSymbolsEntries() {
return localSymbolsEntryList;
public List<NList> getNList(long dylibOffset) {
for (DyldCacheLocalSymbolsEntry entry : localSymbolsEntryList) {
int index = entry.getNListStartIndex();
int count = entry.getNListCount();
if (dylibOffset == entry.getDylibOffset()) {
return nlistList.subList(index, index + count);
}
}
return List.of();
}
@Override

View File

@@ -88,10 +88,10 @@ public class DyldCacheProgramBuilder extends MachoProgramBuilder {
new SplitDyldCache(provider, options.processLocalSymbols(), log, monitor)) {
// Set image base
setDyldCacheImageBase(splitDyldCache.getDyldCacheHeader(0));
setDyldCacheImageBase(splitDyldCache);
// Set entry point
setDyldCacheEntryPoint(splitDyldCache.getDyldCacheHeader(0));
setDyldCacheEntryPoint(splitDyldCache);
// Setup memory
// Check if local symbols are present
@@ -125,25 +125,25 @@ public class DyldCacheProgramBuilder extends MachoProgramBuilder {
/**
* Sets the program's image base.
*
* @param dyldCacheHeader The "base" DYLD Cache header
* @param splitDyldCache The split DYLD cache
* @throws Exception if there was problem setting the program's image base
*/
private void setDyldCacheImageBase(DyldCacheHeader dyldCacheHeader) throws Exception {
private void setDyldCacheImageBase(SplitDyldCache splitDyldCache) throws Exception {
monitor.setMessage("Setting image base...");
monitor.initialize(1);
program.setImageBase(space.getAddress(dyldCacheHeader.getBaseAddress()), true);
program.setImageBase(space.getAddress(splitDyldCache.getBaseAddress()), true);
monitor.incrementProgress(1);
}
/**
* Sets the program's entry point (if known).
*
* @param dyldCacheHeader The "base" DYLD Cache header
* @param splitDyldCache The split DYLD cache
* @throws Exception if there was problem setting the program's entry point
*/
private void setDyldCacheEntryPoint(DyldCacheHeader dyldCacheHeader) throws Exception {
private void setDyldCacheEntryPoint(SplitDyldCache splitDyldCache) throws Exception {
monitor.initialize(1, "Setting entry pointer base...");
Long entryPoint = dyldCacheHeader.getEntryPoint();
Long entryPoint = splitDyldCache.getDyldCacheHeader(0).getEntryPoint();
if (entryPoint != null) {
Address entryPointAddr = space.getAddress(entryPoint);
program.getSymbolTable().addExternalEntryPoint(entryPointAddr);

View File

@@ -218,6 +218,30 @@ public class DyldCacheUtils {
public int size() {
return providers.size();
}
/**
* Gets the base address of the split DYLD cache. This is where the cache should be loaded
* in memory.
*
* @return The base address of the split DYLD cache
*/
public long getBaseAddress() {
return headers.get(0).getBaseAddress();
}
/**
* Gets the {@link DyldCacheLocalSymbolsInfo} from the split DYLD Cache files
*
* @return The {@link DyldCacheLocalSymbolsInfo} from the split DYLD Cache files, or null
* if no local symbols are defined
*/
public DyldCacheLocalSymbolsInfo getLocalSymbolInfo() {
return headers.stream()
.map(h -> h.getLocalSymbolsInfo())
.filter(info -> info != null)
.findAny()
.orElse(null);
}
@Override
public void close() throws IOException {

View File

@@ -22,10 +22,12 @@ import org.apache.commons.collections4.BidiMap;
import org.apache.commons.collections4.bidimap.DualHashBidiMap;
import org.jdom.JDOMException;
import ghidra.app.util.bin.*;
import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.bin.ByteProviderWrapper;
import ghidra.app.util.bin.format.macho.MachException;
import ghidra.app.util.bin.format.macho.MachHeader;
import ghidra.app.util.bin.format.macho.commands.*;
import ghidra.app.util.bin.format.macho.commands.SegmentCommand;
import ghidra.app.util.bin.format.macho.commands.SegmentNames;
import ghidra.app.util.bin.format.macho.prelink.*;
import ghidra.util.Msg;
import ghidra.util.task.TaskMonitor;
@@ -44,26 +46,9 @@ public class MachoPrelinkUtils {
*/
public static boolean isMachoPrelink(ByteProvider provider, TaskMonitor monitor) {
try {
MachHeader header = new MachHeader(provider);
BinaryReader reader = new BinaryReader(provider, header.isLittleEndian());
reader.setPointerIndex(header.getSize());
// Doing a full header parse is too slow...we really just need to see if a segment
// exists that starts with __PRELINK. Parse the minimal amount to do that check.
for (int i = 0; i < header.getNumberOfCommands(); i++) {
int type = reader.peekNextInt();
if (type == LoadCommandTypes.LC_SEGMENT || type == LoadCommandTypes.LC_SEGMENT_64) {
SegmentCommand segment = new SegmentCommand(reader, header.is32bit());
if (segment.getSegmentName().startsWith("__PRELINK")) {
return true;
}
}
else {
type = reader.readNextInt();
int size = reader.readNextInt();
reader.setPointerIndex(reader.getPointerIndex() + size - 8);
}
}
return new MachHeader(provider).parseSegments()
.stream()
.anyMatch(segment -> segment.getSegmentName().startsWith("__PRELINK"));
}
catch (MachException | IOException e) {
// Assume it's not a Mach-O PRELINK...fall through

View File

@@ -195,8 +195,14 @@ public class DyldCacheDylibExtractor {
}
byte[] bytes;
if (segment == linkEditSegment) {
bytes =
createPackedLinkEditSegment(segmentProvider, packedLinkEditSize);
bytes = createPackedLinkEditSegment(segmentProvider, packedLinkEditSize);
// We don't want our packed __LINKEDIT segment to overlap with other DYLIB's
// that might get extracted and added to the same program. Rather than
// computing the optimal address it should go at (which will required looking
// at every other DYLIB in the cache which is slow), just make the address very
// far away from the other DYLIB's
segment.setVMaddress(textSegment.getVMaddress() << 4);
}
else {
bytes = segmentProvider.readBytes(segment.getFileOffset(), segmentSize);
@@ -221,28 +227,15 @@ public class DyldCacheDylibExtractor {
}
/**
* Gets a {@link List} of local {@link NList symbol}s
* Gets a {@link List} of local {@link NList symbol}s for the DYLIB being extracted
*
* @param splitDyldCache The {@link SplitDyldCache}
* @return A {@link List} of local {@link NList symbol}s (could be empty)
*/
private List<NList> getLocalSymbols(SplitDyldCache splitDyldCache) {
long base = splitDyldCache.getDyldCacheHeader(0).getBaseAddress();
for (int i = 0; i < splitDyldCache.size(); i++) {
DyldCacheHeader header = splitDyldCache.getDyldCacheHeader(i);
DyldCacheLocalSymbolsInfo info = header.getLocalSymbolsInfo();
if (info == null) {
continue;
}
for (DyldCacheLocalSymbolsEntry entry : info.getLocalSymbolsEntries()) {
int index = entry.getNListStartIndex();
int count = entry.getNListCount();
if (base + entry.getDylibOffset() == textSegment.getVMaddress() && count > 0) {
return info.getNList().subList(index, index + count);
}
}
}
return List.of();
long base = splitDyldCache.getBaseAddress();
DyldCacheLocalSymbolsInfo info = splitDyldCache.getLocalSymbolInfo();
return info != null ? info.getNList(textSegment.getVMaddress() - base) : List.of();
}
/**
@@ -378,14 +371,18 @@ public class DyldCacheDylibExtractor {
*/
private void fixupSegment(SegmentCommand segment, boolean is64bit) throws IOException {
long adjustment = packedSegmentAdjustments.getOrDefault(segment, 0);
if (segment.getFileOffset() > 0) {
fixup(segment.getStartIndex() + (is64bit ? 0x28 : 0x20), adjustment,
is64bit ? 8 : 4, segment);
if (segment.getVMaddress() > 0) {
set(segment.getStartIndex() + (is64bit ? 0x18 : 0x18), segment.getVMaddress(),
is64bit ? 8 : 4);
}
if (segment.getVMsize() > 0) {
set(segment.getStartIndex() + (is64bit ? 0x20 : 0x1c), segment.getVMsize(),
is64bit ? 8 : 4);
}
if (segment.getFileOffset() > 0) {
fixup(segment.getStartIndex() + (is64bit ? 0x28 : 0x20), adjustment,
is64bit ? 8 : 4, segment);
}
if (segment.getFileSize() > 0) {
set(segment.getStartIndex() + (is64bit ? 0x30 : 0x24), segment.getFileSize(),
is64bit ? 8 : 4);

View File

@@ -36,6 +36,7 @@ import ghidra.util.task.TaskMonitor;
public class DyldCacheFileSystem extends GFileSystemBase {
private SplitDyldCache splitDyldCache;
private boolean parsedLocalSymbols = false;
private Map<DyldCacheSlideInfoCommon, List<DyldCacheSlideFixup>> slideFixupMap;
private Map<GFile, Long> addrMap = new HashMap<>();
private Map<GFile, Integer> indexMap = new HashMap<>();
@@ -47,9 +48,13 @@ public class DyldCacheFileSystem extends GFileSystemBase {
@Override
public void close() throws IOException {
slideFixupMap = null;
parsedLocalSymbols = false;
addrMap.clear();
indexMap.clear();
splitDyldCache.close();
if (splitDyldCache != null) {
splitDyldCache.close();
splitDyldCache = null;
}
super.close();
}
@@ -68,6 +73,14 @@ public class DyldCacheFileSystem extends GFileSystemBase {
slideFixupMap = DyldCacheDylibExtractor.getSlideFixups(splitDyldCache, monitor);
}
if (!parsedLocalSymbols) {
for (int i = 0; i < splitDyldCache.size(); i++) {
splitDyldCache.getDyldCacheHeader(i)
.parseLocalSymbolsInfo(true, new MessageLog(), monitor);
}
parsedLocalSymbols = true;
}
try {
return DyldCacheDylibExtractor.extractDylib(machHeaderStartIndexInProvider,
splitDyldCache, index, slideFixupMap, file.getFSRL(), monitor);
@@ -120,7 +133,7 @@ public class DyldCacheFileSystem extends GFileSystemBase {
MessageLog log = new MessageLog();
monitor.setMessage("Opening DYLD cache...");
splitDyldCache = new SplitDyldCache(provider, true, log, monitor);
splitDyldCache = new SplitDyldCache(provider, false, log, monitor);
for (int i = 0; i < splitDyldCache.size(); i++) {
DyldCacheHeader header = splitDyldCache.getDyldCacheHeader(i);
monitor.setMessage("Find files...");