blob: 7ec29a64cd8e9db81a574dbc6e07399c23d19381 [file] [log] [blame]
// This file is part of class-dump, a utility for examining the Objective-C segment of Mach-O files.
// Copyright (C) 1997-1998, 2000-2001, 2004-2006 Steve Nygard
#import "CDClassDump.h"
#import <Foundation/Foundation.h>
#import "NSArray-Extensions.h"
#import "CDDylibCommand.h"
#import "CDFatArch.h"
#import "CDFatFile.h"
#import "CDMachOFile.h"
#import "CDObjCSegmentProcessor.h"
#import "CDStructureTable.h"
#import "CDSymbolReferences.h"
#import "CDType.h"
#import "CDTypeFormatter.h"
#import "CDTypeParser.h"
@implementation CDClassDump
static NSMutableSet *wrapperExtensions = nil;
+ (void)initialize;
{
// TODO (old): Try grabbing these from an environment variable.
wrapperExtensions = [[NSMutableSet alloc] init];
[wrapperExtensions addObject:@"app"];
[wrapperExtensions addObject:@"framework"];
[wrapperExtensions addObject:@"bundle"];
[wrapperExtensions addObject:@"palette"];
[wrapperExtensions addObject:@"plugin"];
}
// How does this handle something ending in "/"?
+ (BOOL)isWrapperAtPath:(NSString *)path;
{
return [wrapperExtensions containsObject:[path pathExtension]];
}
+ (NSString *)pathToMainFileOfWrapper:(NSString *)wrapperPath;
{
NSString *base, *extension, *mainFile;
base = [wrapperPath lastPathComponent];
extension = [base pathExtension];
base = [base stringByDeletingPathExtension];
if ([@"framework" isEqual:extension] == YES) {
mainFile = [NSString stringWithFormat:@"%@/%@", wrapperPath, base];
} else {
// app, bundle, palette, plugin
mainFile = [NSString stringWithFormat:@"%@/Contents/MacOS/%@", wrapperPath, base];
}
return mainFile;
}
// Allow user to specify wrapper instead of the actual Mach-O file.
+ (NSString *)adjustUserSuppliedPath:(NSString *)path;
{
NSString *fullyResolvedPath, *basePath, *resolvedBasePath;
if ([self isWrapperAtPath:path] == YES) {
path = [self pathToMainFileOfWrapper:path];
}
fullyResolvedPath = [path stringByResolvingSymlinksInPath];
basePath = [path stringByDeletingLastPathComponent];
resolvedBasePath = [basePath stringByResolvingSymlinksInPath];
//NSLog(@"fullyResolvedPath: %@", fullyResolvedPath);
//NSLog(@"basePath: %@", basePath);
//NSLog(@"resolvedBasePath: %@", resolvedBasePath);
// I don't want to resolve all of the symlinks, just the ones starting from the wrapper.
// If I have a symlink from my home directory to /System/Library/Frameworks/AppKit.framework, I want to see the
// path to my home directory.
// This is an easy way to cheat so that we don't have to deal with NSFileManager ourselves.
// This is clever, but it fails when the symlink goes outside of the wrapper. For example, currently
// /System/Library/PrivateFrameworks/ICACameraPriv.framework/ICACameraPriv is a symbolic link to
// ../../Frameworks/ICADevices.framework/Versions/A/ICADevices and so now we check to make sure the
// first parts of the paths are the same.
if ([fullyResolvedPath hasPrefix:resolvedBasePath] == NO)
return fullyResolvedPath;
return [basePath stringByAppendingString:[fullyResolvedPath substringFromIndex:[resolvedBasePath length]]];
}
- (id)init;
{
if ([super init] == nil)
return nil;
executablePath = nil;
machOFilesByID = [[NSMutableDictionary alloc] init];
objCSegmentProcessors = [[NSMutableArray alloc] init];
structureTable = [[CDStructureTable alloc] init];
[structureTable setAnonymousBaseName:@"CDAnonymousStruct"];
[structureTable setName:@"Structs"];
unionTable = [[CDStructureTable alloc] init];
[unionTable setAnonymousBaseName:@"CDAnonymousUnion"];
[unionTable setName:@"Unions"];
ivarTypeFormatter = [[CDTypeFormatter alloc] init];
[ivarTypeFormatter setShouldExpand:NO];
[ivarTypeFormatter setShouldAutoExpand:YES];
[ivarTypeFormatter setBaseLevel:1];
[ivarTypeFormatter setDelegate:self];
methodTypeFormatter = [[CDTypeFormatter alloc] init];
[methodTypeFormatter setShouldExpand:NO];
[methodTypeFormatter setShouldAutoExpand:NO];
[methodTypeFormatter setBaseLevel:0];
[methodTypeFormatter setDelegate:self];
structDeclarationTypeFormatter = [[CDTypeFormatter alloc] init];
[structDeclarationTypeFormatter setShouldExpand:YES]; // But don't expand named struct members...
[structDeclarationTypeFormatter setShouldAutoExpand:YES];
[structDeclarationTypeFormatter setBaseLevel:0];
[structDeclarationTypeFormatter setDelegate:self]; // But need to ignore some things?
frameworkNamesByClassName = [[NSMutableDictionary alloc] init];
preferredCPUType = CPU_TYPE_ANY;
//preferredCPUType = CPU_TYPE_POWERPC;
//preferredCPUType = CPU_TYPE_I386;
flags.shouldShowHeader = YES;
return self;
}
- (void)dealloc;
{
[executablePath release];
[outputPath release];
[machOFilesByID release];
[objCSegmentProcessors release];
[structureTable release];
[unionTable release];
[ivarTypeFormatter release];
[methodTypeFormatter release];
[structDeclarationTypeFormatter release];
[frameworkNamesByClassName release];
if (flags.shouldMatchRegex == YES)
regfree(&compiledRegex);
[super dealloc];
}
- (NSString *)executablePath;
{
return executablePath;
}
- (void)setExecutablePath:(NSString *)newPath;
{
if (newPath == executablePath)
return;
[executablePath release];
executablePath = [newPath retain];
}
- (BOOL)shouldProcessRecursively;
{
return flags.shouldProcessRecursively;
}
- (void)setShouldProcessRecursively:(BOOL)newFlag;
{
flags.shouldProcessRecursively = newFlag;
}
- (BOOL)shouldGenerateSeparateHeaders;
{
return flags.shouldGenerateSeparateHeaders;
}
- (void)setShouldGenerateSeparateHeaders:(BOOL)newFlag;
{
flags.shouldGenerateSeparateHeaders = newFlag;
}
- (BOOL)shouldSortClasses;
{
return flags.shouldSortClasses;
}
- (void)setShouldSortClasses:(BOOL)newFlag;
{
flags.shouldSortClasses = newFlag;
}
- (BOOL)shouldSortClassesByInheritance;
{
return flags.shouldSortClassesByInheritance;
}
- (void)setShouldSortClassesByInheritance:(BOOL)newFlag;
{
flags.shouldSortClassesByInheritance = newFlag;
}
- (BOOL)shouldSortMethods;
{
return flags.shouldSortMethods;
}
- (void)setShouldSortMethods:(BOOL)newFlag;
{
flags.shouldSortMethods = newFlag;
}
- (BOOL)shouldShowIvarOffsets;
{
return flags.shouldShowIvarOffsets;
}
- (void)setShouldShowIvarOffsets:(BOOL)newFlag;
{
flags.shouldShowIvarOffsets = newFlag;
}
- (BOOL)shouldShowMethodAddresses;
{
return flags.shouldShowMethodAddresses;
}
- (void)setShouldShowMethodAddresses:(BOOL)newFlag;
{
flags.shouldShowMethodAddresses = newFlag;
}
- (BOOL)shouldMatchRegex;
{
return flags.shouldMatchRegex;
}
- (void)setShouldMatchRegex:(BOOL)newFlag;
{
if (flags.shouldMatchRegex == YES && newFlag == NO)
regfree(&compiledRegex);
flags.shouldMatchRegex = newFlag;
}
- (BOOL)shouldShowHeader;
{
return flags.shouldShowHeader;
}
- (void)setShouldShowHeader:(BOOL)newFlag;
{
flags.shouldShowHeader = newFlag;
}
- (BOOL)setRegex:(char *)regexCString errorMessage:(NSString **)errorMessagePointer;
{
int result;
if (flags.shouldMatchRegex == YES)
regfree(&compiledRegex);
result = regcomp(&compiledRegex, regexCString, REG_EXTENDED);
if (result != 0) {
char regex_error_buffer[256];
if (regerror(result, &compiledRegex, regex_error_buffer, 256) > 0) {
if (errorMessagePointer != NULL) {
*errorMessagePointer = [NSString stringWithCString:regex_error_buffer];
}
} else {
if (errorMessagePointer != NULL)
*errorMessagePointer = nil;
}
return NO;
}
[self setShouldMatchRegex:YES];
return YES;
}
- (BOOL)regexMatchesString:(NSString *)aString;
{
int result;
result = regexec(&compiledRegex, [aString UTF8String], 0, NULL, 0);
if (result != 0) {
if (result != REG_NOMATCH) {
char regex_error_buffer[256];
if (regerror(result, &compiledRegex, regex_error_buffer, 256) > 0)
NSLog(@"Error with regex matching string, %@", [NSString stringWithCString:regex_error_buffer]);
}
return NO;
}
return YES;
}
- (NSString *)outputPath;
{
return outputPath;
}
- (void)setOutputPath:(NSString *)aPath;
{
if (aPath == outputPath)
return;
[outputPath release];
outputPath = [aPath retain];
}
- (cpu_type_t)preferredCPUType;
{
return preferredCPUType;
}
- (void)setPreferredCPUType:(cpu_type_t)aPreferredCPUType;
{
preferredCPUType = aPreferredCPUType;
}
- (BOOL)containsObjectiveCSegments;
{
unsigned int count, index;
count = [objCSegmentProcessors count];
for (index = 0; index < count; index++) {
if ([[objCSegmentProcessors objectAtIndex:index] hasModules])
return YES;
}
return NO;
}
- (CDStructureTable *)structureTable;
{
return structureTable;
}
- (CDStructureTable *)unionTable;
{
return unionTable;
}
- (CDTypeFormatter *)ivarTypeFormatter;
{
return ivarTypeFormatter;
}
- (CDTypeFormatter *)methodTypeFormatter;
{
return methodTypeFormatter;
}
- (CDTypeFormatter *)structDeclarationTypeFormatter;
{
return structDeclarationTypeFormatter;
}
// Return YES if successful, NO if there was an error.
- (BOOL)processFilename:(NSString *)aFilename;
{
NSString *adjustedPath;
adjustedPath = [[self class] adjustUserSuppliedPath:aFilename];
[self setExecutablePath:[adjustedPath stringByDeletingLastPathComponent]];
return [self _processFilename:adjustedPath];
}
// Return YES if successful, NO if there was an error.
- (BOOL)_processFilename:(NSString *)aFilename;
{
CDFatFile *aFatFile;
CDMachOFile *aMachOFile;
CDObjCSegmentProcessor *aProcessor;
// TODO (2005-07-08): This isn't good enough. You only have your
// choice on the main file. Link frameworks MUST be the same
// architecture, either as a stand-alone Mach-O file or within a fat file.
// Initial combinations:
// 1. macho file, no cpu preference
// 2. macho file, cpu preference same as macho file
// 3. macho file, cpu preference different from macho file
// 4. fat file, no cpu preference
// 5. fat file, cpu preference contained in fat file
// 6. fat file, cpu preference not contained in fat file
//
// Actions:
// 1, 2, 4, 5: All subsequent files must be same cpu
// 3. Print message saying that arch isn't available in this macho file
// 6. Print message saying that arch isn't available in this fat file
//
// For linked frameworks/libraries, if the arch isn't available silently skip?
aFatFile = [[CDFatFile alloc] initWithFilename:aFilename];
if (aFatFile == nil) {
aMachOFile = [[CDMachOFile alloc] initWithFilename:aFilename];
if (aMachOFile == nil) {
fprintf(stderr, "class-dump: Input file (%s) is neither a Mach-O file nor a fat archive.\n", [aFilename fileSystemRepresentation]);
return NO;
}
if (preferredCPUType == CPU_TYPE_ANY) {
preferredCPUType = [aMachOFile cpuType];
} else if ([aMachOFile cpuType] != preferredCPUType) {
fprintf(stderr, "class-dump: Mach-O file (%s) does not contain required cpu type: %s.\n",
[aFilename fileSystemRepresentation], [CDNameForCPUType(preferredCPUType) UTF8String]);
[aMachOFile release];
return NO;
}
} else {
CDFatArch *fatArch;
fatArch = [aFatFile fatArchWithCPUType:preferredCPUType];
if (fatArch == nil) {
if (preferredCPUType == CPU_TYPE_ANY)
fprintf(stderr, "class-dump: Fat archive (%s) did not contain any cpu types!\n", [aFilename fileSystemRepresentation]);
else
fprintf(stderr, "class-dump: Fat archive (%s) does not contain required cpu type: %s.\n",
[aFilename fileSystemRepresentation], [CDNameForCPUType(preferredCPUType) UTF8String]);
[aFatFile release];
return NO;
}
if (preferredCPUType == CPU_TYPE_ANY) {
preferredCPUType = [fatArch cpuType];
}
aMachOFile = [[CDMachOFile alloc] initWithFilename:aFilename archiveOffset:[fatArch offset]];
[aFatFile release];
if (aMachOFile == nil)
return NO;
}
[aMachOFile setDelegate:self];
// TODO (2005-07-03): Look for the newer exception handling stuff.
NS_DURING {
[aMachOFile process];
} NS_HANDLER {
[aMachOFile release];
return NO;
} NS_ENDHANDLER;
aProcessor = [[CDObjCSegmentProcessor alloc] initWithMachOFile:aMachOFile];
[aProcessor process];
[objCSegmentProcessors addObject:aProcessor];
[aProcessor release];
assert([aMachOFile filename] != nil);
[machOFilesByID setObject:aMachOFile forKey:[aMachOFile filename]];
[aMachOFile release];
return YES;
}
- (void)generateOutput;
{
[self registerPhase:1];
[self registerPhase:2];
[self generateMemberNames];
if ([self shouldGenerateSeparateHeaders] == YES)
[self generateSeparateHeaders];
else
[self generateToStandardOut];
}
- (void)generateToStandardOut;
{
NSMutableString *resultString;
int count, index;
NSData *data;
resultString = [[NSMutableString alloc] init];
[self appendHeaderToString:resultString];
if ([self containsObjectiveCSegments]) {
[self appendStructuresToString:resultString symbolReferences:nil];
count = [objCSegmentProcessors count];
for (index = 0; index < count; index++) {
[[objCSegmentProcessors objectAtIndex:index] appendFormattedString:resultString classDump:self];
}
} else {
[resultString appendString:@"This file does not contain any Objective-C runtime information.\n"];
}
data = [resultString dataUsingEncoding:NSUTF8StringEncoding];
[(NSFileHandle *)[NSFileHandle fileHandleWithStandardOutput] writeData:data];
[resultString release];
}
- (void)generateSeparateHeaders;
{
int count, index;
if ([self containsObjectiveCSegments] == NO) {
NSLog(@"Warning: This file does not contain any Objective-C runtime information.");
return;
}
[self buildClassFrameworks];
if (outputPath != nil) {
NSFileManager *fileManager;
BOOL isDirectory;
fileManager = [NSFileManager defaultManager];
if ([fileManager fileExistsAtPath:outputPath isDirectory:&isDirectory] == NO) {
BOOL result;
result = [fileManager createDirectoryAtPath:outputPath attributes:nil];
if (result == NO) {
NSLog(@"Error: Couldn't create output directory: %@", outputPath);
return;
}
} else if (isDirectory == NO) {
NSLog(@"Error: File exists at output path: %@", outputPath);
return;
}
}
[self generateStructureHeader];
count = [objCSegmentProcessors count];
for (index = 0; index < count; index++) {
[[objCSegmentProcessors objectAtIndex:index] generateSeparateHeadersClassDump:self];
}
}
- (void)generateStructureHeader;
{
NSMutableString *resultString;
NSString *filename;
CDSymbolReferences *symbolReferences;
NSString *referenceString;
unsigned int referenceIndex;
resultString = [[NSMutableString alloc] init];
[self appendHeaderToString:resultString];
symbolReferences = [[CDSymbolReferences alloc] init];
referenceIndex = [resultString length];
[self appendStructuresToString:resultString symbolReferences:symbolReferences];
referenceString = [symbolReferences referenceString];
if (referenceString != nil)
[resultString insertString:referenceString atIndex:referenceIndex];
filename = @"CDStructures.h";
if (outputPath != nil)
filename = [outputPath stringByAppendingPathComponent:filename];
[[resultString dataUsingEncoding:NSUTF8StringEncoding] writeToFile:filename atomically:YES];
[symbolReferences release];
[resultString release];
}
- (void)logInfo;
{
[structureTable logInfo];
[unionTable logInfo];
}
- (void)appendStructuresToString:(NSMutableString *)resultString symbolReferences:(CDSymbolReferences *)symbolReferences;
{
[structureTable appendNamedStructuresToString:resultString classDump:self formatter:structDeclarationTypeFormatter symbolReferences:symbolReferences];
[structureTable appendTypedefsToString:resultString classDump:self formatter:structDeclarationTypeFormatter symbolReferences:symbolReferences];
[unionTable appendNamedStructuresToString:resultString classDump:self formatter:structDeclarationTypeFormatter symbolReferences:symbolReferences];
[unionTable appendTypedefsToString:resultString classDump:self formatter:structDeclarationTypeFormatter symbolReferences:symbolReferences];
}
- (CDMachOFile *)machOFileWithID:(NSString *)anID;
{
NSString *adjustedID;
CDMachOFile *aMachOFile;
NSString *replacementString = @"@executable_path";
if ([anID hasPrefix:replacementString] == YES) {
adjustedID = [executablePath stringByAppendingString:[anID substringFromIndex:[replacementString length]]];
} else {
adjustedID = anID;
}
aMachOFile = [machOFilesByID objectForKey:adjustedID];
if (aMachOFile == nil) {
[self _processFilename:adjustedID];
aMachOFile = [machOFilesByID objectForKey:adjustedID];
}
return aMachOFile;
}
- (void)machOFile:(CDMachOFile *)aMachOFile loadDylib:(CDDylibCommand *)aDylibCommand;
{
if ([aDylibCommand cmd] == LC_LOAD_DYLIB && [self shouldProcessRecursively] == YES)
[self machOFileWithID:[aDylibCommand name]];
}
- (void)appendHeaderToString:(NSMutableString *)resultString;
{
// Since this changes each version, for regression testing it'll be better to be able to not show it.
if (flags.shouldShowHeader == NO)
return;
[resultString appendString:@"/*\n"];
[resultString appendFormat:@" * Generated by class-dump %@.\n", CLASS_DUMP_VERSION];
[resultString appendString:@" *\n"];
[resultString appendString:@" * class-dump is Copyright (C) 1997-1998, 2000-2001, 2004-2006 by Steve Nygard.\n"];
[resultString appendString:@" */\n\n"];
}
- (CDType *)typeFormatter:(CDTypeFormatter *)aFormatter replacementForType:(CDType *)aType;
{
if ([aType type] == '{')
return [structureTable replacementForType:aType];
if ([aType type] == '(')
return [unionTable replacementForType:aType];
return nil;
}
- (NSString *)typeFormatter:(CDTypeFormatter *)aFormatter typedefNameForStruct:(CDType *)structType level:(int)level;
{
CDType *replacementType, *searchType;
CDStructureTable *targetTable;
if (level == 0 && aFormatter == structDeclarationTypeFormatter)
return nil;
if ([structType type] == '{') {
targetTable = structureTable;
} else {
targetTable = unionTable;
}
// We need to catch top level replacements, not just replacements for struct members.
replacementType = [targetTable replacementForType:structType];
if (replacementType != nil)
searchType = replacementType;
else
searchType = structType;
return [targetTable typedefNameForStructureType:searchType];
}
- (void)registerPhase:(int)phase;
{
NSAutoreleasePool *pool;
int count, index;
pool = [[NSAutoreleasePool alloc] init];
count = [objCSegmentProcessors count];
for (index = 0; index < count; index++) {
[[objCSegmentProcessors objectAtIndex:index] registerStructuresWithObject:self phase:phase];
}
[self endPhase:phase];
[pool release];
}
- (void)endPhase:(int)phase;
{
if (phase == 1) {
[structureTable finishPhase1];
[unionTable finishPhase1];
} else if (phase == 2) {
[structureTable generateNamesForAnonymousStructures];
[unionTable generateNamesForAnonymousStructures];
}
}
- (void)phase1RegisterStructure:(CDType *)aStructure;
{
if ([aStructure type] == '{') {
[structureTable phase1RegisterStructure:aStructure];
} else if ([aStructure type] == '(') {
[unionTable phase1RegisterStructure:aStructure];
} else {
NSLog(@"%s, unknown structure type: %d", _cmd, [aStructure type]);
}
}
- (BOOL)phase2RegisterStructure:(CDType *)aStructure usedInMethod:(BOOL)isUsedInMethod countReferences:(BOOL)shouldCountReferences;
{
if ([aStructure type] == '{') {
return [structureTable phase2RegisterStructure:aStructure withObject:self usedInMethod:isUsedInMethod countReferences:shouldCountReferences];
} else if ([aStructure type] == '(') {
return [unionTable phase2RegisterStructure:aStructure withObject:self usedInMethod:isUsedInMethod countReferences:shouldCountReferences];
} else {
NSLog(@"%s, unknown structure type: %d", _cmd, [aStructure type]);
}
return NO;
}
- (void)generateMemberNames;
{
[structureTable generateMemberNames];
[unionTable generateMemberNames];
}
- (void)buildClassFrameworks;
{
[objCSegmentProcessors makeObjectsPerformSelector:@selector(registerClassesWithObject:) withObject:frameworkNamesByClassName];
}
- (NSString *)frameworkForClassName:(NSString *)aClassName;
{
return [frameworkNamesByClassName objectForKey:aClassName];
}
- (void)appendImportForClassName:(NSString *)aClassName toString:(NSMutableString *)resultString;
{
if (aClassName != nil) {
NSString *classFramework;
classFramework = [self frameworkForClassName:aClassName];
if (classFramework == nil)
[resultString appendFormat:@"#import \"%@.h\"\n\n", aClassName];
else
[resultString appendFormat:@"#import <%@/%@.h>\n\n", classFramework, aClassName];
}
}
@end