| // -*- mode: ObjC -*- |
| |
| // 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-2013 Steve Nygard. |
| |
| #include <stdio.h> |
| #include <libc.h> |
| #include <unistd.h> |
| #include <getopt.h> |
| #include <stdlib.h> |
| #include <mach-o/arch.h> |
| |
| #import "CDClassDump.h" |
| #import "CDFindMethodVisitor.h" |
| #import "CDClassDumpVisitor.h" |
| #import "CDMultiFileVisitor.h" |
| #import "CDFile.h" |
| #import "CDMachOFile.h" |
| #import "CDFatFile.h" |
| #import "CDFatArch.h" |
| #import "CDSearchPathState.h" |
| |
| void print_usage(void) |
| { |
| fprintf(stderr, |
| "class-dump %s\n" |
| "Usage: class-dump [options] <mach-o-file>\n" |
| "\n" |
| " where options are:\n" |
| " -a show instance variable offsets\n" |
| " -A show implementation addresses\n" |
| " --arch <arch> choose a specific architecture from a universal binary (ppc, ppc64, i386, x86_64, armv6, armv7, armv7s, arm64)\n" |
| " -C <regex> only display classes matching regular expression\n" |
| " -f <str> find string in method name\n" |
| " -H generate header files in current directory, or directory specified with -o\n" |
| " -I sort classes, categories, and protocols by inheritance (overrides -s)\n" |
| " -o <dir> output directory used for -H\n" |
| " -r recursively expand frameworks and fixed VM shared libraries\n" |
| " -s sort classes and categories by name\n" |
| " -S sort methods by name\n" |
| " -t suppress header in output, for testing\n" |
| " --list-arches list the arches in the file, then exit\n" |
| " --sdk-ios specify iOS SDK version (will look in /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS<version>.sdk\n" |
| " --sdk-mac specify Mac OS X version (will look in /Developer/SDKs/MacOSX<version>.sdk\n" |
| " --sdk-root specify the full SDK root path (or use --sdk-ios/--sdk-mac for a shortcut)\n" |
| , |
| CLASS_DUMP_VERSION |
| ); |
| } |
| |
| #define CD_OPT_ARCH 1 |
| #define CD_OPT_LIST_ARCHES 2 |
| #define CD_OPT_VERSION 3 |
| #define CD_OPT_SDK_IOS 4 |
| #define CD_OPT_SDK_MAC 5 |
| #define CD_OPT_SDK_ROOT 6 |
| #define CD_OPT_HIDE 7 |
| |
| int main(int argc, char *argv[]) |
| { |
| @autoreleasepool { |
| NSString *searchString; |
| BOOL shouldGenerateSeparateHeaders = NO; |
| BOOL shouldListArches = NO; |
| BOOL shouldPrintVersion = NO; |
| CDArch targetArch; |
| BOOL hasSpecifiedArch = NO; |
| NSString *outputPath; |
| NSMutableSet *hiddenSections = [NSMutableSet set]; |
| |
| int ch; |
| BOOL errorFlag = NO; |
| |
| struct option longopts[] = { |
| { "show-ivar-offsets", no_argument, NULL, 'a' }, |
| { "show-imp-addr", no_argument, NULL, 'A' }, |
| { "match", required_argument, NULL, 'C' }, |
| { "find", required_argument, NULL, 'f' }, |
| { "generate-multiple-files", no_argument, NULL, 'H' }, |
| { "sort-by-inheritance", no_argument, NULL, 'I' }, |
| { "output-dir", required_argument, NULL, 'o' }, |
| { "recursive", no_argument, NULL, 'r' }, |
| { "sort", no_argument, NULL, 's' }, |
| { "sort-methods", no_argument, NULL, 'S' }, |
| { "arch", required_argument, NULL, CD_OPT_ARCH }, |
| { "list-arches", no_argument, NULL, CD_OPT_LIST_ARCHES }, |
| { "suppress-header", no_argument, NULL, 't' }, |
| { "version", no_argument, NULL, CD_OPT_VERSION }, |
| { "sdk-ios", required_argument, NULL, CD_OPT_SDK_IOS }, |
| { "sdk-mac", required_argument, NULL, CD_OPT_SDK_MAC }, |
| { "sdk-root", required_argument, NULL, CD_OPT_SDK_ROOT }, |
| { "hide", required_argument, NULL, CD_OPT_HIDE }, |
| { NULL, 0, NULL, 0 }, |
| }; |
| |
| if (argc == 1) { |
| print_usage(); |
| exit(0); |
| } |
| |
| CDClassDump *classDump = [[CDClassDump alloc] init]; |
| |
| while ( (ch = getopt_long(argc, argv, "aAC:f:HIo:rRsSt", longopts, NULL)) != -1) { |
| switch (ch) { |
| case CD_OPT_ARCH: { |
| NSString *name = [NSString stringWithUTF8String:optarg]; |
| targetArch = CDArchFromName(name); |
| if (targetArch.cputype != CPU_TYPE_ANY) |
| hasSpecifiedArch = YES; |
| else { |
| fprintf(stderr, "Error: Unknown arch %s\n\n", optarg); |
| errorFlag = YES; |
| } |
| break; |
| } |
| |
| case CD_OPT_LIST_ARCHES: |
| shouldListArches = YES; |
| break; |
| |
| case CD_OPT_VERSION: |
| shouldPrintVersion = YES; |
| break; |
| |
| case CD_OPT_SDK_IOS: { |
| NSString *root = [NSString stringWithUTF8String:optarg]; |
| //NSLog(@"root: %@", root); |
| NSString *str = [NSString stringWithFormat:@"/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS%@.sdk", root]; |
| classDump.sdkRoot = str; |
| |
| break; |
| } |
| |
| case CD_OPT_SDK_MAC: { |
| NSString *root = [NSString stringWithUTF8String:optarg]; |
| //NSLog(@"root: %@", root); |
| NSString *str = [NSString stringWithFormat:@"/Developer/SDKs/MacOSX%@.sdk", root]; |
| classDump.sdkRoot = str; |
| |
| break; |
| } |
| |
| case CD_OPT_SDK_ROOT: { |
| NSString *root = [NSString stringWithUTF8String:optarg]; |
| //NSLog(@"root: %@", root); |
| classDump.sdkRoot = root; |
| |
| break; |
| } |
| |
| case CD_OPT_HIDE: { |
| NSString *str = [NSString stringWithUTF8String:optarg]; |
| if ([str isEqualToString:@"all"]) { |
| [hiddenSections addObject:@"structures"]; |
| [hiddenSections addObject:@"protocols"]; |
| } else { |
| [hiddenSections addObject:str]; |
| } |
| break; |
| } |
| |
| case 'a': |
| classDump.shouldShowIvarOffsets = YES; |
| break; |
| |
| case 'A': |
| classDump.shouldShowMethodAddresses = YES; |
| break; |
| |
| case 'C': { |
| NSError *error; |
| NSRegularExpression *regularExpression = [NSRegularExpression regularExpressionWithPattern:[NSString stringWithUTF8String:optarg] |
| options:(NSRegularExpressionOptions)0 |
| error:&error]; |
| if (regularExpression != nil) { |
| classDump.regularExpression = regularExpression; |
| } else { |
| fprintf(stderr, "class-dump: Error with regular expression: %s\n\n", [[error localizedFailureReason] UTF8String]); |
| errorFlag = YES; |
| } |
| |
| // Last one wins now. |
| break; |
| } |
| |
| case 'f': { |
| searchString = [NSString stringWithUTF8String:optarg]; |
| break; |
| } |
| |
| case 'H': |
| shouldGenerateSeparateHeaders = YES; |
| break; |
| |
| case 'I': |
| classDump.shouldSortClassesByInheritance = YES; |
| break; |
| |
| case 'o': |
| outputPath = [NSString stringWithUTF8String:optarg]; |
| break; |
| |
| case 'r': |
| classDump.shouldProcessRecursively = YES; |
| break; |
| |
| case 's': |
| classDump.shouldSortClasses = YES; |
| break; |
| |
| case 'S': |
| classDump.shouldSortMethods = YES; |
| break; |
| |
| case 't': |
| classDump.shouldShowHeader = NO; |
| break; |
| |
| case '?': |
| default: |
| errorFlag = YES; |
| break; |
| } |
| } |
| |
| if (errorFlag) { |
| print_usage(); |
| exit(2); |
| } |
| |
| if (shouldPrintVersion) { |
| printf("class-dump %s compiled %s\n", CLASS_DUMP_VERSION, __DATE__ " " __TIME__); |
| exit(0); |
| } |
| |
| if (optind < argc) { |
| NSString *arg = [NSString stringWithFileSystemRepresentation:argv[optind]]; |
| NSString *executablePath = [arg executablePathForFilename]; |
| if (shouldListArches) { |
| if (executablePath == nil) { |
| printf("none\n"); |
| } else { |
| CDSearchPathState *searchPathState = [[CDSearchPathState alloc] init]; |
| searchPathState.executablePath = executablePath; |
| id macho = [CDFile fileWithContentsOfFile:executablePath searchPathState:searchPathState]; |
| if (macho == nil) { |
| printf("none\n"); |
| } else { |
| if ([macho isKindOfClass:[CDMachOFile class]]) { |
| printf("%s\n", [[macho archName] UTF8String]); |
| } else if ([macho isKindOfClass:[CDFatFile class]]) { |
| printf("%s\n", [[[macho archNames] componentsJoinedByString:@" "] UTF8String]); |
| } |
| } |
| } |
| } else { |
| if (executablePath == nil) { |
| fprintf(stderr, "class-dump: Input file (%s) doesn't contain an executable.\n", [arg fileSystemRepresentation]); |
| exit(1); |
| } |
| |
| classDump.searchPathState.executablePath = [executablePath stringByDeletingLastPathComponent]; |
| CDFile *file = [CDFile fileWithContentsOfFile:executablePath searchPathState:classDump.searchPathState]; |
| if (file == nil) { |
| NSFileManager *defaultManager = [NSFileManager defaultManager]; |
| |
| if ([defaultManager fileExistsAtPath:executablePath]) { |
| if ([defaultManager isReadableFileAtPath:executablePath]) { |
| fprintf(stderr, "class-dump: Input file (%s) is neither a Mach-O file nor a fat archive.\n", [executablePath UTF8String]); |
| } else { |
| fprintf(stderr, "class-dump: Input file (%s) is not readable (check read permissions).\n", [executablePath UTF8String]); |
| } |
| } else { |
| fprintf(stderr, "class-dump: Input file (%s) does not exist.\n", [executablePath UTF8String]); |
| } |
| |
| exit(1); |
| } |
| |
| if (hasSpecifiedArch == NO) { |
| if ([file bestMatchForLocalArch:&targetArch] == NO) { |
| fprintf(stderr, "Error: Couldn't get local architecture\n"); |
| exit(1); |
| } |
| //NSLog(@"No arch specified, best match for local arch is: (%08x, %08x)", targetArch.cputype, targetArch.cpusubtype); |
| } else { |
| //NSLog(@"chosen arch is: (%08x, %08x)", targetArch.cputype, targetArch.cpusubtype); |
| } |
| |
| classDump.targetArch = targetArch; |
| classDump.searchPathState.executablePath = [executablePath stringByDeletingLastPathComponent]; |
| |
| NSError *error; |
| if (![classDump loadFile:file error:&error]) { |
| fprintf(stderr, "Error: %s\n", [[error localizedFailureReason] UTF8String]); |
| exit(1); |
| } else { |
| [classDump processObjectiveCData]; |
| [classDump registerTypes]; |
| |
| if (searchString != nil) { |
| CDFindMethodVisitor *visitor = [[CDFindMethodVisitor alloc] init]; |
| visitor.classDump = classDump; |
| visitor.searchString = searchString; |
| [classDump recursivelyVisit:visitor]; |
| } else if (shouldGenerateSeparateHeaders) { |
| CDMultiFileVisitor *multiFileVisitor = [[CDMultiFileVisitor alloc] init]; |
| multiFileVisitor.classDump = classDump; |
| classDump.typeController.delegate = multiFileVisitor; |
| multiFileVisitor.outputPath = outputPath; |
| [classDump recursivelyVisit:multiFileVisitor]; |
| } else { |
| CDClassDumpVisitor *visitor = [[CDClassDumpVisitor alloc] init]; |
| visitor.classDump = classDump; |
| if ([hiddenSections containsObject:@"structures"]) visitor.shouldShowStructureSection = NO; |
| if ([hiddenSections containsObject:@"protocols"]) visitor.shouldShowProtocolSection = NO; |
| [classDump recursivelyVisit:visitor]; |
| } |
| } |
| } |
| } |
| } |
| |
| return 0; |
| } |