blob: 8902f838b0f81eaef9f39e1af18ce2c32f734128 [file] [log] [blame]
// -*- mode: ObjC -*-
// This file is part of class-dump, a utility for examining the Objective-C segment of Mach-O files.
// Copyright (C) 1997-2019 Steve Nygard.
#import "CDMachOFile.h"
#include <mach-o/arch.h>
#include <mach-o/loader.h>
#include <mach-o/fat.h>
#import "CDMachOFileDataCursor.h"
#import "CDFatFile.h"
#import "CDLoadCommand.h"
#import "CDLCDyldInfo.h"
#import "CDLCDylib.h"
#import "CDLCDynamicSymbolTable.h"
#import "CDLCEncryptionInfo.h"
#import "CDLCRunPath.h"
#import "CDLCSegment.h"
#import "CDLCSymbolTable.h"
#import "CDLCUUID.h"
#import "CDLCVersionMinimum.h"
#import "CDObjectiveC1Processor.h"
#import "CDObjectiveC2Processor.h"
#import "CDSection.h"
#import "CDSymbol.h"
#import "CDRelocationInfo.h"
#import "CDSearchPathState.h"
#import "CDLCSourceVersion.h"
#import "CDLCBuildVersion.h"
static NSString *CDMachOFileMagicNumberDescription(uint32_t magic)
{
switch (magic) {
case MH_MAGIC: return @"MH_MAGIC";
case MH_CIGAM: return @"MH_CIGAM";
case MH_MAGIC_64: return @"MH_MAGIC_64";
case MH_CIGAM_64: return @"MH_CIGAM_64";
}
return [NSString stringWithFormat:@"0x%08x", magic];
}
@implementation CDMachOFile
{
CDByteOrder _byteOrder;
NSArray *_loadCommands;
NSArray *_dylibLoadCommands;
NSArray *_segments;
CDLCSymbolTable *_symbolTable;
CDLCDynamicSymbolTable *_dynamicSymbolTable;
CDLCDyldInfo *_dyldInfo;
CDLCDylib *_dylibIdentifier;
CDLCVersionMinimum *_minVersionMacOSX;
CDLCVersionMinimum *_minVersionIOS;
CDLCSourceVersion *_sourceVersion;
CDLCBuildVersion *_buildVersion;
NSArray *_runPaths;
NSArray *_runPathCommands;
NSArray *_dyldEnvironment;
NSArray *_reExportedDylibs;
// The parts of struct mach_header_64 pulled out so that our property accessors can be synthesized.
uint32_t _magic;
cpu_type_t _cputype;
cpu_subtype_t _cpusubtype;
uint32_t _filetype;
uint32_t _ncmds;
uint32_t _sizeofcmds;
uint32_t _flags;
uint32_t _reserved;
BOOL _uses64BitABI;
}
- (id)init;
{
if ((self = [super init])) {
_byteOrder = CDByteOrder_LittleEndian;
}
return self;
}
- (id)initWithData:(NSData *)data filename:(NSString *)filename searchPathState:(CDSearchPathState *)searchPathState;
{
if ((self = [super initWithData:data filename:filename searchPathState:searchPathState])) {
_byteOrder = CDByteOrder_LittleEndian;
CDDataCursor *cursor = [[CDDataCursor alloc] initWithData:data];
_magic = [cursor readBigInt32];
if (_magic == MH_MAGIC || _magic == MH_MAGIC_64) {
_byteOrder = CDByteOrder_BigEndian;
} else if (_magic == MH_CIGAM || _magic == MH_CIGAM_64) {
_byteOrder = CDByteOrder_LittleEndian;
} else {
return nil;
}
_uses64BitABI = (_magic == MH_MAGIC_64) || (_magic == MH_CIGAM_64);
if (_byteOrder == CDByteOrder_LittleEndian) {
_cputype = [cursor readLittleInt32];
_cpusubtype = [cursor readLittleInt32];
_filetype = [cursor readLittleInt32];
_ncmds = [cursor readLittleInt32];
_sizeofcmds = [cursor readLittleInt32];
_flags = [cursor readLittleInt32];
if (_uses64BitABI) {
_reserved = [cursor readLittleInt32];
}
} else {
_cputype = [cursor readBigInt32];
_cpusubtype = [cursor readBigInt32];
_filetype = [cursor readBigInt32];
_ncmds = [cursor readBigInt32];
_sizeofcmds = [cursor readBigInt32];
_flags = [cursor readBigInt32];
if (_uses64BitABI) {
_reserved = [cursor readBigInt32];
}
}
NSAssert(_uses64BitABI == CDArchUses64BitABI((CDArch){ .cputype = _cputype, .cpusubtype = _cpusubtype }), @"Header magic should match cpu arch", nil);
NSUInteger headerOffset = _uses64BitABI ? sizeof(struct mach_header_64) : sizeof(struct mach_header);
CDMachOFileDataCursor *fileCursor = [[CDMachOFileDataCursor alloc] initWithFile:self offset:headerOffset];
[self _readLoadCommands:fileCursor count:_ncmds];
}
return self;
}
- (void)_readLoadCommands:(CDMachOFileDataCursor *)cursor count:(uint32_t)count;
{
NSMutableArray *loadCommands = [[NSMutableArray alloc] init];
NSMutableArray *dylibLoadCommands = [[NSMutableArray alloc] init];
NSMutableArray *segments = [[NSMutableArray alloc] init];
NSMutableArray *runPaths = [[NSMutableArray alloc] init];
NSMutableArray *runPathCommands = [[NSMutableArray alloc] init];
NSMutableArray *dyldEnvironment = [[NSMutableArray alloc] init];
NSMutableArray *reExportedDylibs = [[NSMutableArray alloc] init];
for (uint32_t index = 0; index < count; index++) {
CDLoadCommand *loadCommand = [CDLoadCommand loadCommandWithDataCursor:cursor];
if (loadCommand != nil) {
[loadCommands addObject:loadCommand];
if (loadCommand.cmd == LC_VERSION_MIN_MACOSX) self.minVersionMacOSX = (CDLCVersionMinimum *)loadCommand;
if (loadCommand.cmd == LC_VERSION_MIN_IPHONEOS) self.minVersionIOS = (CDLCVersionMinimum *)loadCommand;
if (loadCommand.cmd == LC_DYLD_ENVIRONMENT) [dyldEnvironment addObject:loadCommand];
if (loadCommand.cmd == LC_REEXPORT_DYLIB) [reExportedDylibs addObject:loadCommand];
if (loadCommand.cmd == LC_ID_DYLIB) self.dylibIdentifier = (CDLCDylib *)loadCommand;
if ([loadCommand isKindOfClass:[CDLCSourceVersion class]]) self.sourceVersion = (CDLCSourceVersion *)loadCommand;
else if ([loadCommand isKindOfClass:[CDLCBuildVersion class]]) self.buildVersion = (CDLCBuildVersion *)loadCommand;
else if ([loadCommand isKindOfClass:[CDLCDylib class]]) [dylibLoadCommands addObject:loadCommand];
else if ([loadCommand isKindOfClass:[CDLCSegment class]]) [segments addObject:loadCommand];
else if ([loadCommand isKindOfClass:[CDLCSymbolTable class]]) self.symbolTable = (CDLCSymbolTable *)loadCommand;
else if ([loadCommand isKindOfClass:[CDLCDynamicSymbolTable class]]) self.dynamicSymbolTable = (CDLCDynamicSymbolTable *)loadCommand;
else if ([loadCommand isKindOfClass:[CDLCDyldInfo class]]) self.dyldInfo = (CDLCDyldInfo *)loadCommand;
else if ([loadCommand isKindOfClass:[CDLCRunPath class]]) {
[runPaths addObject:[(CDLCRunPath *)loadCommand resolvedRunPath]];
[runPathCommands addObject:loadCommand];
}
}
//NSLog(@"loadCommand: %@", loadCommand);
}
_loadCommands = [loadCommands copy];
_dylibLoadCommands = [dylibLoadCommands copy];
_segments = [segments copy];
_runPaths = [runPaths copy];
_runPathCommands = [runPathCommands copy];
_dyldEnvironment = [dyldEnvironment copy];
_reExportedDylibs = [reExportedDylibs copy];
for (CDLoadCommand *loadCommand in _loadCommands) {
[loadCommand machOFileDidReadLoadCommands:self];
}
}
#pragma mark - Debugging
- (NSString *)description;
{
return [NSString stringWithFormat:@"<%@:%p> magic: 0x%08x, cputype: %x, cpusubtype: %x, filetype: %d, ncmds: %ld, sizeofcmds: %d, flags: 0x%x, uses64BitABI? %d, filename: %@, data: %p",
NSStringFromClass([self class]), self,
[self magic], [self cputype], [self cpusubtype], [self filetype], [_loadCommands count], 0, [self flags], self.uses64BitABI,
self.filename, self.data];
}
#pragma mark -
- (CDMachOFile *)machOFileWithArch:(CDArch)arch;
{
if (self.cputype == arch.cputype && self.maskedCPUSubtype == (arch.cpusubtype & ~CPU_SUBTYPE_MASK))
return self;
return nil;
}
#pragma mark -
- (cpu_type_t)maskedCPUType;
{
return self.cputype & ~CPU_ARCH_MASK;
}
- (cpu_subtype_t)maskedCPUSubtype;
{
return self.cpusubtype & ~CPU_SUBTYPE_MASK;
}
- (NSUInteger)ptrSize;
{
return self.uses64BitABI ? sizeof(uint64_t) : sizeof(uint32_t);
}
// We only have one architecture, so it is by default the best match.
- (BOOL)bestMatchForArch:(CDArch *)ioArchPtr;
{
if (ioArchPtr != NULL) {
ioArchPtr->cputype = self.cputype;
ioArchPtr->cpusubtype = self.cpusubtype;
}
return YES;
}
- (NSString *)filetypeDescription;
{
switch ([self filetype]) {
case MH_OBJECT: return @"OBJECT";
case MH_EXECUTE: return @"EXECUTE";
case MH_FVMLIB: return @"FVMLIB";
case MH_CORE: return @"CORE";
case MH_PRELOAD: return @"PRELOAD";
case MH_DYLIB: return @"DYLIB";
case MH_DYLINKER: return @"DYLINKER";
case MH_BUNDLE: return @"BUNDLE";
case MH_DYLIB_STUB: return @"DYLIB_STUB";
case MH_DSYM: return @"DSYM";
case MH_KEXT_BUNDLE: return @"KEXT_BUNDLE";
default:
break;
}
return nil;
}
- (NSString *)flagDescription;
{
NSMutableArray *setFlags = [NSMutableArray array];
uint32_t flags = [self flags];
if (flags & MH_NOUNDEFS) [setFlags addObject:@"NOUNDEFS"];
if (flags & MH_INCRLINK) [setFlags addObject:@"INCRLINK"];
if (flags & MH_DYLDLINK) [setFlags addObject:@"DYLDLINK"];
if (flags & MH_BINDATLOAD) [setFlags addObject:@"BINDATLOAD"];
if (flags & MH_PREBOUND) [setFlags addObject:@"PREBOUND"];
if (flags & MH_SPLIT_SEGS) [setFlags addObject:@"SPLIT_SEGS"];
if (flags & MH_LAZY_INIT) [setFlags addObject:@"LAZY_INIT"];
if (flags & MH_TWOLEVEL) [setFlags addObject:@"TWOLEVEL"];
if (flags & MH_FORCE_FLAT) [setFlags addObject:@"FORCE_FLAT"];
if (flags & MH_NOMULTIDEFS) [setFlags addObject:@"NOMULTIDEFS"];
if (flags & MH_NOFIXPREBINDING) [setFlags addObject:@"NOFIXPREBINDING"];
if (flags & MH_PREBINDABLE) [setFlags addObject:@"PREBINDABLE"];
if (flags & MH_ALLMODSBOUND) [setFlags addObject:@"ALLMODSBOUND"];
if (flags & MH_SUBSECTIONS_VIA_SYMBOLS) [setFlags addObject:@"SUBSECTIONS_VIA_SYMBOLS"];
if (flags & MH_CANONICAL) [setFlags addObject:@"CANONICAL"];
if (flags & MH_WEAK_DEFINES) [setFlags addObject:@"WEAK_DEFINES"];
if (flags & MH_BINDS_TO_WEAK) [setFlags addObject:@"BINDS_TO_WEAK"];
if (flags & MH_ALLOW_STACK_EXECUTION) [setFlags addObject:@"ALLOW_STACK_EXECUTION"];
if (flags & MH_ROOT_SAFE) [setFlags addObject:@"ROOT_SAFE"];
if (flags & MH_SETUID_SAFE) [setFlags addObject:@"SETUID_SAFE"];
if (flags & MH_NO_REEXPORTED_DYLIBS) [setFlags addObject:@"NO_REEXPORTED_DYLIBS"];
if (flags & MH_PIE) [setFlags addObject:@"PIE"];
return [setFlags componentsJoinedByString:@" "];
}
#pragma mark -
- (CDLCSegment *)dataConstSegment
{
// macho objects from iOS 9 appear to store various sections
// in __DATA_CONST that were previously found in __DATA
CDLCSegment *seg = [self segmentWithName:@"__DATA_CONST"];
// Fall back on __DATA if it is not found for earlier behavior
if (!seg) {
seg = [self segmentWithName:@"__DATA"];
}
return seg;
}
- (CDLCSegment *)segmentWithName:(NSString *)segmentName;
{
for (id loadCommand in _loadCommands) {
if ([loadCommand isKindOfClass:[CDLCSegment class]] && [[loadCommand name] isEqual:segmentName]) {
return loadCommand;
}
}
return nil;
}
- (CDLCSegment *)segmentContainingAddress:(NSUInteger)address;
{
for (id loadCommand in _loadCommands) {
if ([loadCommand isKindOfClass:[CDLCSegment class]] && [loadCommand containsAddress:address]) {
return loadCommand;
}
}
return nil;
}
- (void)showWarning:(NSString *)warning;
{
NSLog(@"Warning: %@", warning);
}
- (NSString *)stringAtAddress:(NSUInteger)address;
{
const void *ptr;
if (address == 0)
return nil;
CDLCSegment *segment = [self segmentContainingAddress:address];
if (segment == nil) {
NSLog(@"Error: Cannot find offset for address 0x%08lx in stringAtAddress:", address);
exit(5);
return nil;
}
if ([segment isProtected]) {
NSData *d2 = [segment decryptedData];
NSUInteger d2Offset = [segment segmentOffsetForAddress:address];
if (d2Offset == 0)
return nil;
ptr = (uint8_t *)[d2 bytes] + d2Offset;
return [[NSString alloc] initWithBytes:ptr length:strlen(ptr) encoding:NSASCIIStringEncoding];
}
NSUInteger offset = [self dataOffsetForAddress:address];
if (offset == 0)
return nil;
ptr = (uint8_t *)[self.data bytes] + offset;
return [[NSString alloc] initWithBytes:ptr length:strlen(ptr) encoding:NSASCIIStringEncoding];
}
- (NSUInteger)dataOffsetForAddress:(NSUInteger)address;
{
if (address == 0)
return 0;
CDLCSegment *segment = [self segmentContainingAddress:address];
if (segment == nil) {
NSLog(@"Error: Cannot find offset for address 0x%08lx in dataOffsetForAddress:", address);
exit(5);
}
// if ([segment isProtected]) {
// NSLog(@"Error: Segment is protected.");
// exit(5);
// }
#if 0
NSLog(@"---------->");
NSLog(@"segment is: %@", segment);
NSLog(@"address: 0x%08x", address);
NSLog(@"CDFile offset: 0x%08x", offset);
NSLog(@"file off for address: 0x%08x", [segment fileOffsetForAddress:address]);
NSLog(@"data offset: 0x%08x", offset + [segment fileOffsetForAddress:address]);
NSLog(@"<----------");
#endif
return [segment fileOffsetForAddress:address];
}
- (const void *)bytes;
{
return [self.data bytes];
}
- (const void *)bytesAtOffset:(NSUInteger)offset;
{
return (uint8_t *)[self.data bytes] + offset;
}
- (NSString *)importBaseName;
{
if ([self filetype] == MH_DYLIB) {
return CDImportNameForPath(self.filename);
}
return nil;
}
#pragma mark -
- (BOOL)isEncrypted;
{
for (CDLoadCommand *loadCommand in _loadCommands) {
if ([loadCommand isKindOfClass:[CDLCEncryptionInfo class]] && [(CDLCEncryptionInfo *)loadCommand isEncrypted]) {
return YES;
}
}
return NO;
}
- (BOOL)hasProtectedSegments;
{
for (CDLoadCommand *loadCommand in _loadCommands) {
if ([loadCommand isKindOfClass:[CDLCSegment class]] && [(CDLCSegment *)loadCommand isProtected])
return YES;
}
return NO;
}
- (BOOL)canDecryptAllSegments;
{
for (CDLoadCommand *loadCommand in _loadCommands) {
if ([loadCommand isKindOfClass:[CDLCSegment class]] && [(CDLCSegment *)loadCommand canDecrypt] == NO)
return NO;
}
return YES;
}
- (NSString *)loadCommandString:(BOOL)isVerbose;
{
NSMutableString *resultString = [NSMutableString string];
NSUInteger count = [_loadCommands count];
for (NSUInteger index = 0; index < count; index++) {
[resultString appendFormat:@"Load command %lu\n", index];
CDLoadCommand *loadCommand = _loadCommands[index];
[loadCommand appendToString:resultString verbose:isVerbose];
[resultString appendString:@"\n"];
}
return resultString;
}
- (NSString *)headerString:(BOOL)isVerbose;
{
NSMutableString *resultString = [NSMutableString string];
[resultString appendString:@"Mach header\n"];
[resultString appendString:@" magic cputype cpusubtype filetype ncmds sizeofcmds flags\n"];
// Grr, %11@ doesn't work.
if (isVerbose)
[resultString appendFormat:@"%11@ %7@ %10u %8@ %5lu %10u %@\n",
CDMachOFileMagicNumberDescription([self magic]), [self archName], [self cpusubtype],
[self filetypeDescription], [_loadCommands count], 0, [self flagDescription]];
else
[resultString appendFormat:@" 0x%08x %7u %10u %8u %5lu %10u 0x%08x\n",
[self magic], [self cputype], [self cpusubtype], [self filetype], [_loadCommands count], 0, [self flags]];
[resultString appendString:@"\n"];
return resultString;
}
- (NSUUID *)UUID;
{
for (CDLoadCommand *loadCommand in _loadCommands)
if ([loadCommand isKindOfClass:[CDLCUUID class]])
return [(CDLCUUID *)loadCommand UUID];
return nil;
}
// Must not return nil.
- (NSString *)archName;
{
return CDNameForCPUType([self cputype], [self cpusubtype]);
}
- (void)logInfoForAddress:(NSUInteger)address;
{
if (address != 0) {
CDLCSegment *segment = [self segmentContainingAddress:address];
if (segment == nil) {
NSLog(@"No segment contains address: %016lx", address);
} else {
//NSLog(@"Found address %016lx in segment, sections= %@", address, [segment sections]);
CDSection *section = [segment sectionContainingAddress:address];
if (section == nil) {
NSLog(@"Found address %016lx in segment %@, but not in a section", address, [segment name]);
} else {
NSLog(@"Found address %016lx in segment %@, section %@", address, [segment name], [section sectionName]);
}
}
NSString *str = [self stringAtAddress:address];
NSLog(@" address %016lx as a string: '%@' (length %lu)", address, str, [str length]);
NSLog(@" address %016lx data offset: %lu", address, [self dataOffsetForAddress:address]);
}
}
- (NSString *)externalClassNameForAddress:(NSUInteger)address;
{
// Not for NSCFArray (NSMutableArray), NSSimpleAttributeDictionaryEnumerator (NSEnumerator), NSSimpleAttributeDictionary (NSDictionary), etc.
// It turns out NSMutableArray is in /System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation, so...
// ... it's an undefined symbol, need to look it up.
CDRelocationInfo *rinfo = [self.dynamicSymbolTable relocationEntryWithOffset:address - [self.symbolTable baseAddress]];
//NSLog(@"rinfo: %@", rinfo);
if (rinfo != nil) {
CDSymbol *symbol = [[self.symbolTable symbols] objectAtIndex:rinfo.symbolnum];
//NSLog(@"symbol: %@", symbol);
// Now we could use GET_LIBRARY_ORDINAL(), look up the the appropriate mach-o file (being sure to have loaded them even without -r),
// look up the symbol in that mach-o file, get the address, look up the class based on that address, and finally get the class name
// from that.
// Or, we could be lazy and take advantage of the fact that the class name we're after is in the symbol name:
NSString *str = [symbol name];
if ([str hasPrefix:ObjCClassSymbolPrefix]) {
return [str substringFromIndex:[ObjCClassSymbolPrefix length]];
} else {
NSLog(@"Warning: Unknown prefix on symbol name... %@ (addr %lx)", str, address);
return str;
}
}
// This is fine, they might really be root objects. NSObject, NSProxy.
return nil;
}
- (BOOL)hasRelocationEntryForAddress:(NSUInteger)address;
{
CDRelocationInfo *rinfo = [self.dynamicSymbolTable relocationEntryWithOffset:address - [self.symbolTable baseAddress]];
//NSLog(@"%s, rinfo= %@", __cmd, rinfo);
return rinfo != nil;
}
- (BOOL)hasRelocationEntryForAddress2:(NSUInteger)address;
{
return [self.dyldInfo symbolNameForAddress:address] != nil;
}
- (NSString *)externalClassNameForAddress2:(NSUInteger)address;
{
NSString *str = [self.dyldInfo symbolNameForAddress:address];
if (str != nil) {
if ([str hasPrefix:ObjCClassSymbolPrefix]) {
return [str substringFromIndex:[ObjCClassSymbolPrefix length]];
} else {
NSLog(@"Warning: Unknown prefix on symbol name... %@ (addr %lx)", str, address);
return str;
}
}
return nil;
}
- (BOOL)hasObjectiveC1Data;
{
return [self segmentWithName:@"__OBJC"] != nil;
}
- (BOOL)hasObjectiveC2Data;
{
// http://twitter.com/gparker/status/17962955683
// Oxced: What's the best way to determine the ObjC ABI version of a file? otool tests if cputype is ARM, but that's not accurate with iOS 4 simulator
// gparker: @0xced Old ABI has an __OBJC segment. New ABI has a __DATA,__objc_info section.
// 0xced: @gparker I was hoping for a flag, but that will do it, thanks.
// 0xced: @gparker Did you mean __DATA,__objc_imageinfo instead of __DATA,__objc_info ?
// gparker: @0xced Yes, it's __DATA,__objc_imageinfo.
return [[self dataConstSegment] sectionWithName:@"__objc_imageinfo"] != nil;
}
- (Class)processorClass;
{
if ([self hasObjectiveC2Data])
return [CDObjectiveC2Processor class];
return [CDObjectiveC1Processor class];
}
- (CDLCDylib *)dylibLoadCommandForLibraryOrdinal:(NSUInteger)libraryOrdinal;
{
if (libraryOrdinal == SELF_LIBRARY_ORDINAL || libraryOrdinal >= MAX_LIBRARY_ORDINAL)
return nil;
NSArray *loadCommands = _dylibLoadCommands;
if (_dylibIdentifier != nil) {
// Remove our own ID (LC_ID_DYLIB) so that we calculate the correct offset
NSMutableArray *remainingLoadCommands = [loadCommands mutableCopy];
[remainingLoadCommands removeObject:_dylibIdentifier];
loadCommands = remainingLoadCommands;
}
if (libraryOrdinal - 1 < [loadCommands count]) // Ordinals start from 1
return loadCommands[libraryOrdinal - 1];
else
return nil;
}
- (NSString *)architectureNameDescription;
{
return self.archName;
}
@end