blob: 2003fcb04d8c75d10f3ca602a5f8d41bd0b978dd [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 "CDObjectiveC2Processor.h"
#import "CDMachOFile.h"
#import "CDSection.h"
#import "CDLCSegment.h"
#import "CDMachOFileDataCursor.h"
#import "CDOCClass.h"
#import "CDOCMethod.h"
#import "CDOCInstanceVariable.h"
#import "CDLCSymbolTable.h"
#import "CDOCCategory.h"
#import "CDClassDump.h"
#import "CDSymbol.h"
#import "CDOCProperty.h"
#import "cd_objc2.h"
#import "CDProtocolUniquer.h"
#import "CDOCClassReference.h"
@implementation CDObjectiveC2Processor
{
}
- (void)loadProtocols;
{
CDSection *section = [[self.machOFile dataConstSegment] sectionWithName:@"__objc_protolist"];
CDMachOFileDataCursor *cursor = [[CDMachOFileDataCursor alloc] initWithSection:section];
while ([cursor isAtEnd] == NO)
[self protocolAtAddress:[cursor readPtr]];
}
- (void)loadClasses;
{
CDSection *section = [[self.machOFile dataConstSegment] sectionWithName:@"__objc_classlist"];
CDMachOFileDataCursor *cursor = [[CDMachOFileDataCursor alloc] initWithSection:section];
while ([cursor isAtEnd] == NO) {
uint64_t val = [cursor readPtr];
CDOCClass *aClass = [self loadClassAtAddress:val];
if (aClass != nil) {
[self addClass:aClass withAddress:val];
}
}
}
- (void)loadCategories;
{
CDSection *section = [[self.machOFile dataConstSegment] sectionWithName:@"__objc_catlist"];
CDMachOFileDataCursor *cursor = [[CDMachOFileDataCursor alloc] initWithSection:section];
while ([cursor isAtEnd] == NO) {
CDOCCategory *category = [self loadCategoryAtAddress:[cursor readPtr]];
[self addCategory:category];
}
}
- (CDOCProtocol *)protocolAtAddress:(uint64_t)address;
{
if (address == 0)
return nil;
CDOCProtocol *protocol = [self.protocolUniquer protocolWithAddress:address];
if (protocol == nil) {
protocol = [[CDOCProtocol alloc] init];
[self.protocolUniquer setProtocol:protocol withAddress:address];
CDMachOFileDataCursor *cursor = [[CDMachOFileDataCursor alloc] initWithFile:self.machOFile address:address];
NSParameterAssert([cursor offset] != 0);
struct cd_objc2_protocol objc2Protocol;
objc2Protocol.isa = [cursor readPtr];
objc2Protocol.name = [cursor readPtr];
objc2Protocol.protocols = [cursor readPtr];
objc2Protocol.instanceMethods = [cursor readPtr];
objc2Protocol.classMethods = [cursor readPtr];
objc2Protocol.optionalInstanceMethods = [cursor readPtr];
objc2Protocol.optionalClassMethods = [cursor readPtr];
objc2Protocol.instanceProperties = [cursor readPtr];
objc2Protocol.size = [cursor readInt32];
objc2Protocol.flags = [cursor readInt32];
objc2Protocol.extendedMethodTypes = 0;
CDMachOFileDataCursor *extendedMethodTypesCursor = nil;
BOOL hasExtendedMethodTypesField = objc2Protocol.size > 8 * [self.machOFile ptrSize] + 2 * sizeof(uint32_t);
if (hasExtendedMethodTypesField) {
objc2Protocol.extendedMethodTypes = [cursor readPtr];
if (objc2Protocol.extendedMethodTypes != 0) {
extendedMethodTypesCursor = [[CDMachOFileDataCursor alloc] initWithFile:self.machOFile address:objc2Protocol.extendedMethodTypes];
NSParameterAssert([extendedMethodTypesCursor offset] != 0);
}
}
//NSLog(@"----------------------------------------");
//NSLog(@"%016lx %016lx %016lx %016lx", objc2Protocol.isa, objc2Protocol.name, objc2Protocol.protocols, objc2Protocol.instanceMethods);
//NSLog(@"%016lx %016lx %016lx %016lx", objc2Protocol.classMethods, objc2Protocol.optionalInstanceMethods, objc2Protocol.optionalClassMethods, objc2Protocol.instanceProperties);
NSString *str = [self.machOFile stringAtAddress:objc2Protocol.name];
[protocol setName:str];
if (objc2Protocol.protocols != 0) {
[cursor setAddress:objc2Protocol.protocols];
uint64_t count = [cursor readPtr];
for (uint64_t index = 0; index < count; index++) {
uint64_t val = [cursor readPtr];
CDOCProtocol *anotherProtocol = [self protocolAtAddress:val];
if (anotherProtocol != nil) {
[protocol addProtocol:anotherProtocol];
} else {
NSLog(@"Note: another protocol was nil.");
}
}
}
for (CDOCMethod *method in [self loadMethodsAtAddress:objc2Protocol.instanceMethods extendedMethodTypesCursor:extendedMethodTypesCursor])
[protocol addInstanceMethod:method];
for (CDOCMethod *method in [self loadMethodsAtAddress:objc2Protocol.classMethods extendedMethodTypesCursor:extendedMethodTypesCursor])
[protocol addClassMethod:method];
for (CDOCMethod *method in [self loadMethodsAtAddress:objc2Protocol.optionalInstanceMethods extendedMethodTypesCursor:extendedMethodTypesCursor])
[protocol addOptionalInstanceMethod:method];
for (CDOCMethod *method in [self loadMethodsAtAddress:objc2Protocol.optionalClassMethods extendedMethodTypesCursor:extendedMethodTypesCursor])
[protocol addOptionalClassMethod:method];
for (CDOCProperty *property in [self loadPropertiesAtAddress:objc2Protocol.instanceProperties])
[protocol addProperty:property];
}
return protocol;
}
- (CDOCCategory *)loadCategoryAtAddress:(uint64_t)address;
{
if (address == 0)
return nil;
CDMachOFileDataCursor *cursor = [[CDMachOFileDataCursor alloc] initWithFile:self.machOFile address:address];
NSParameterAssert([cursor offset] != 0);
struct cd_objc2_category objc2Category;
objc2Category.name = [cursor readPtr];
objc2Category.class = [cursor readPtr];
objc2Category.instanceMethods = [cursor readPtr];
objc2Category.classMethods = [cursor readPtr];
objc2Category.protocols = [cursor readPtr];
objc2Category.instanceProperties = [cursor readPtr];
objc2Category.v7 = [cursor readPtr];
objc2Category.v8 = [cursor readPtr];
//NSLog(@"----------------------------------------");
//NSLog(@"%016lx %016lx %016lx %016lx", objc2Category.name, objc2Category.class, objc2Category.instanceMethods, objc2Category.classMethods);
//NSLog(@"%016lx %016lx %016lx %016lx", objc2Category.protocols, objc2Category.instanceProperties, objc2Category.v7, objc2Category.v8);
CDOCCategory *category = [[CDOCCategory alloc] init];
NSString *str = [self.machOFile stringAtAddress:objc2Category.name];
[category setName:str];
for (CDOCMethod *method in [self loadMethodsAtAddress:objc2Category.instanceMethods])
[category addInstanceMethod:method];
for (CDOCMethod *method in [self loadMethodsAtAddress:objc2Category.classMethods])
[category addClassMethod:method];
for (CDOCProtocol *protocol in [self.protocolUniquer uniqueProtocolsAtAddresses:[self protocolAddressListAtAddress:objc2Category.protocols]])
[category addProtocol:protocol];
for (CDOCProperty *property in [self loadPropertiesAtAddress:objc2Category.instanceProperties])
[category addProperty:property];
{
uint64_t classNameAddress = address + [self.machOFile ptrSize];
NSString *externalClassName = nil;
if ([self.machOFile hasRelocationEntryForAddress2:classNameAddress]) {
externalClassName = [self.machOFile externalClassNameForAddress2:classNameAddress];
//NSLog(@"category: got external class name (2): %@", [category className]);
} else if ([self.machOFile hasRelocationEntryForAddress:classNameAddress]) {
externalClassName = [self.machOFile externalClassNameForAddress:classNameAddress];
//NSLog(@"category: got external class name (1): %@", [aClass className]);
} else if (objc2Category.class != 0) {
CDOCClass *aClass = [self classWithAddress:objc2Category.class];
category.classRef = [[CDOCClassReference alloc] initWithClassObject:aClass];
}
if (externalClassName != nil) {
CDSymbol *classSymbol = [[self.machOFile symbolTable] symbolForExternalClassName:externalClassName];
if (classSymbol != nil)
category.classRef = [[CDOCClassReference alloc] initWithClassSymbol:classSymbol];
else
category.classRef = [[CDOCClassReference alloc] initWithClassName:externalClassName];
}
}
return category;
}
- (CDOCClass *)loadClassAtAddress:(uint64_t)address;
{
if (address == 0)
return nil;
CDOCClass *class = [self classWithAddress:address];
if (class)
return class;
//NSLog(@"%s, address=%016lx", __cmd, address);
CDMachOFileDataCursor *cursor = [[CDMachOFileDataCursor alloc] initWithFile:self.machOFile address:address];
NSParameterAssert([cursor offset] != 0);
struct cd_objc2_class objc2Class;
objc2Class.isa = [cursor readPtr];
objc2Class.superclass = [cursor readPtr];
objc2Class.cache = [cursor readPtr];
objc2Class.vtable = [cursor readPtr];
uint64_t value = [cursor readPtr];
class.isSwiftClass = (value & 0x1) != 0;
objc2Class.data = value & ~7;
objc2Class.reserved1 = [cursor readPtr];
objc2Class.reserved2 = [cursor readPtr];
objc2Class.reserved3 = [cursor readPtr];
//NSLog(@"%016lx %016lx %016lx %016lx", objc2Class.isa, objc2Class.superclass, objc2Class.cache, objc2Class.vtable);
//NSLog(@"%016lx %016lx %016lx %016lx", objc2Class.data, objc2Class.reserved1, objc2Class.reserved2, objc2Class.reserved3);
NSParameterAssert(objc2Class.data != 0);
[cursor setAddress:objc2Class.data];
struct cd_objc2_class_ro_t objc2ClassData;
objc2ClassData.flags = [cursor readInt32];
objc2ClassData.instanceStart = [cursor readInt32];
objc2ClassData.instanceSize = [cursor readInt32];
if ([self.machOFile uses64BitABI])
objc2ClassData.reserved = [cursor readInt32];
else
objc2ClassData.reserved = 0;
objc2ClassData.ivarLayout = [cursor readPtr];
objc2ClassData.name = [cursor readPtr];
objc2ClassData.baseMethods = [cursor readPtr];
objc2ClassData.baseProtocols = [cursor readPtr];
objc2ClassData.ivars = [cursor readPtr];
objc2ClassData.weakIvarLayout = [cursor readPtr];
objc2ClassData.baseProperties = [cursor readPtr];
//NSLog(@"%08x %08x %08x %08x", objc2ClassData.flags, objc2ClassData.instanceStart, objc2ClassData.instanceSize, objc2ClassData.reserved);
//NSLog(@"%016lx %016lx %016lx %016lx", objc2ClassData.ivarLayout, objc2ClassData.name, objc2ClassData.baseMethods, objc2ClassData.baseProtocols);
//NSLog(@"%016lx %016lx %016lx %016lx", objc2ClassData.ivars, objc2ClassData.weakIvarLayout, objc2ClassData.baseProperties);
NSString *str = [self.machOFile stringAtAddress:objc2ClassData.name];
//NSLog(@"name = %@", str);
CDOCClass *aClass = [[CDOCClass alloc] init];
[aClass setName:str];
for (CDOCMethod *method in [self loadMethodsAtAddress:objc2ClassData.baseMethods])
[aClass addInstanceMethod:method];
aClass.instanceVariables = [self loadIvarsAtAddress:objc2ClassData.ivars];
{
CDSymbol *classSymbol = [[self.machOFile symbolTable] symbolForClassName:str];
if (classSymbol != nil)
aClass.isExported = [classSymbol isExternal];
}
{
uint64_t classNameAddress = address + [self.machOFile ptrSize];
NSString *superClassName = nil;
if ([self.machOFile hasRelocationEntryForAddress2:classNameAddress]) {
superClassName = [self.machOFile externalClassNameForAddress2:classNameAddress];
//NSLog(@"class: got external class name (2): %@", [aClass superClassName]);
} else if ([self.machOFile hasRelocationEntryForAddress:classNameAddress]) {
superClassName = [self.machOFile externalClassNameForAddress:classNameAddress];
//NSLog(@"class: got external class name (1): %@", [aClass superClassName]);
} else if (objc2Class.superclass != 0) {
CDOCClass *sc = [self loadClassAtAddress:objc2Class.superclass];
aClass.superClassRef = [[CDOCClassReference alloc] initWithClassObject:sc];
}
if (superClassName) {
CDSymbol *superClassSymbol = [[self.machOFile symbolTable] symbolForExternalClassName:superClassName];
if (superClassSymbol)
aClass.superClassRef = [[CDOCClassReference alloc] initWithClassSymbol:superClassSymbol];
else
aClass.superClassRef = [[CDOCClassReference alloc] initWithClassName:superClassName];
}
}
for (CDOCMethod *method in [self loadMethodsOfMetaClassAtAddress:objc2Class.isa])
[aClass addClassMethod:method];
// Process protocols
for (CDOCProtocol *protocol in [self.protocolUniquer uniqueProtocolsAtAddresses:[self protocolAddressListAtAddress:objc2ClassData.baseProtocols]])
[aClass addProtocol:protocol];
for (CDOCProperty *property in [self loadPropertiesAtAddress:objc2ClassData.baseProperties])
[aClass addProperty:property];
return aClass;
}
- (NSArray *)loadPropertiesAtAddress:(uint64_t)address;
{
NSMutableArray *properties = [NSMutableArray array];
if (address != 0) {
struct cd_objc2_list_header listHeader;
CDMachOFileDataCursor *cursor = [[CDMachOFileDataCursor alloc] initWithFile:self.machOFile address:address];
NSParameterAssert([cursor offset] != 0);
//NSLog(@"property list data offset: %lu", [cursor offset]);
listHeader.entsize = [cursor readInt32];
listHeader.count = [cursor readInt32];
NSParameterAssert(listHeader.entsize == 2 * [self.machOFile ptrSize]);
for (uint32_t index = 0; index < listHeader.count; index++) {
struct cd_objc2_property objc2Property;
objc2Property.name = [cursor readPtr];
objc2Property.attributes = [cursor readPtr];
NSString *name = [self.machOFile stringAtAddress:objc2Property.name];
NSString *attributes = [self.machOFile stringAtAddress:objc2Property.attributes];
CDOCProperty *property = [[CDOCProperty alloc] initWithName:name attributes:attributes];
[properties addObject:property];
}
}
return properties;
}
// This just gets the methods.
- (NSArray *)loadMethodsOfMetaClassAtAddress:(uint64_t)address;
{
if (address == 0)
return nil;
CDMachOFileDataCursor *cursor = [[CDMachOFileDataCursor alloc] initWithFile:self.machOFile address:address];
NSParameterAssert([cursor offset] != 0);
struct cd_objc2_class objc2Class;
objc2Class.isa = [cursor readPtr];
objc2Class.superclass = [cursor readPtr];
objc2Class.cache = [cursor readPtr];
objc2Class.vtable = [cursor readPtr];
objc2Class.data = [cursor readPtr];
objc2Class.reserved1 = [cursor readPtr];
objc2Class.reserved2 = [cursor readPtr];
objc2Class.reserved3 = [cursor readPtr];
//NSLog(@"%016lx %016lx %016lx %016lx", objc2Class.isa, objc2Class.superclass, objc2Class.cache, objc2Class.vtable);
//NSLog(@"%016lx %016lx %016lx %016lx", objc2Class.data, objc2Class.reserved1, objc2Class.reserved2, objc2Class.reserved3);
NSParameterAssert(objc2Class.data != 0);
[cursor setAddress:objc2Class.data];
struct cd_objc2_class_ro_t objc2ClassData;
objc2ClassData.flags = [cursor readInt32];
objc2ClassData.instanceStart = [cursor readInt32];
objc2ClassData.instanceSize = [cursor readInt32];
if ([self.machOFile uses64BitABI])
objc2ClassData.reserved = [cursor readInt32];
else
objc2ClassData.reserved = 0;
objc2ClassData.ivarLayout = [cursor readPtr];
objc2ClassData.name = [cursor readPtr];
objc2ClassData.baseMethods = [cursor readPtr];
objc2ClassData.baseProtocols = [cursor readPtr];
objc2ClassData.ivars = [cursor readPtr];
objc2ClassData.weakIvarLayout = [cursor readPtr];
objc2ClassData.baseProperties = [cursor readPtr];
return [self loadMethodsAtAddress:objc2ClassData.baseMethods];
}
- (NSArray *)loadMethodsAtAddress:(uint64_t)address;
{
return [self loadMethodsAtAddress:address extendedMethodTypesCursor:nil];
}
- (NSArray *)loadMethodsAtAddress:(uint64_t)address extendedMethodTypesCursor:(CDMachOFileDataCursor *)extendedMethodTypesCursor;
{
NSMutableArray *methods = [NSMutableArray array];
if (address != 0) {
CDMachOFileDataCursor *cursor = [[CDMachOFileDataCursor alloc] initWithFile:self.machOFile address:address];
NSParameterAssert([cursor offset] != 0);
//NSLog(@"method list data offset: %lu", [cursor offset]);
struct cd_objc2_list_header listHeader;
// See getEntsize() from http://www.opensource.apple.com/source/objc4/objc4-532.2/runtime/objc-runtime-new.h
listHeader.entsize = [cursor readInt32] & ~(uint32_t)3;
listHeader.count = [cursor readInt32];
NSParameterAssert(listHeader.entsize == 3 * [self.machOFile ptrSize]);
for (uint32_t index = 0; index < listHeader.count; index++) {
struct cd_objc2_method objc2Method;
objc2Method.name = [cursor readPtr];
objc2Method.types = [cursor readPtr];
objc2Method.imp = [cursor readPtr];
NSString *name = [self.machOFile stringAtAddress:objc2Method.name];
NSString *types = [self.machOFile stringAtAddress:objc2Method.types];
if (extendedMethodTypesCursor) {
uint64_t extendedMethodTypes = [extendedMethodTypesCursor readPtr];
types = [self.machOFile stringAtAddress:extendedMethodTypes];
}
//NSLog(@"%3u: %016lx %016lx %016lx", index, objc2Method.name, objc2Method.types, objc2Method.imp);
//NSLog(@"name: %@", name);
//NSLog(@"types: %@", types);
CDOCMethod *method = [[CDOCMethod alloc] initWithName:name typeString:types address:objc2Method.imp];
[methods addObject:method];
}
}
return [methods reversedArray];
}
- (NSArray *)loadIvarsAtAddress:(uint64_t)address;
{
NSMutableArray *ivars = [NSMutableArray array];
if (address != 0) {
CDMachOFileDataCursor *cursor = [[CDMachOFileDataCursor alloc] initWithFile:self.machOFile address:address];
NSParameterAssert([cursor offset] != 0);
//NSLog(@"ivar list data offset: %lu", [cursor offset]);
struct cd_objc2_list_header listHeader;
listHeader.entsize = [cursor readInt32];
listHeader.count = [cursor readInt32];
NSParameterAssert(listHeader.entsize == 3 * [self.machOFile ptrSize] + 2 * sizeof(uint32_t));
for (uint32_t index = 0; index < listHeader.count; index++) {
struct cd_objc2_ivar objc2Ivar;
objc2Ivar.offset = [cursor readPtr];
objc2Ivar.name = [cursor readPtr];
objc2Ivar.type = [cursor readPtr];
objc2Ivar.alignment = [cursor readInt32];
objc2Ivar.size = [cursor readInt32];
if (objc2Ivar.name != 0) {
NSString *name = [self.machOFile stringAtAddress:objc2Ivar.name];
NSString *typeString = [self.machOFile stringAtAddress:objc2Ivar.type];
CDMachOFileDataCursor *offsetCursor = [[CDMachOFileDataCursor alloc] initWithFile:self.machOFile address:objc2Ivar.offset];
NSUInteger offset = (uint32_t)[offsetCursor readPtr]; // objc-runtime-new.h: "offset is 64-bit by accident" => restrict to 32-bit
CDOCInstanceVariable *ivar = [[CDOCInstanceVariable alloc] initWithName:name typeString:typeString offset:offset];
[ivars addObject:ivar];
} else {
//NSLog(@"%016lx %016lx %016lx %08x %08x", objc2Ivar.offset, objc2Ivar.name, objc2Ivar.type, objc2Ivar.alignment, objc2Ivar.size);
}
}
}
return ivars;
}
// Returns list of NSNumber containing the protocol addresses
- (NSArray *)protocolAddressListAtAddress:(uint64_t)address;
{
NSMutableArray *addresses = [[NSMutableArray alloc] init];;
if (address != 0) {
CDMachOFileDataCursor *cursor = [[CDMachOFileDataCursor alloc] initWithFile:self.machOFile address:address];
uint64_t count = [cursor readPtr];
for (uint64_t index = 0; index < count; index++) {
uint64_t val = [cursor readPtr];
if (val == 0) {
NSLog(@"Warning: protocol address in protocol list was 0.");
} else {
[addresses addObject:[NSNumber numberWithUnsignedLongLong:val]];
}
}
}
return [addresses copy];
}
- (CDSection *)objcImageInfoSection;
{
return [[self.machOFile dataConstSegment] sectionWithName:@"__objc_imageinfo"];
}
@end