commands/FBClassDump.py (246 lines of code) (raw):

#!/usr/bin/python # Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved # # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. import string import fbchisellldbbase as fb import fbchisellldbobjcruntimehelpers as runtimeHelpers def lldbcommands(): return [FBPrintMethods(), FBPrintProperties(), FBPrintBlock()] class FBPrintMethods(fb.FBCommand): def name(self): return "pmethods" def description(self): return "Print the class and instance methods of a class." def options(self): return [ fb.FBCommandArgument( short="-a", long="--address", arg="showaddr", help="Print the implementation address of the method", default=False, boolean=True, ), fb.FBCommandArgument( short="-i", long="--instance", arg="insmethod", help="Print the instance methods", default=False, boolean=True, ), fb.FBCommandArgument( short="-c", long="--class", arg="clsmethod", help="Print the class methods", default=False, boolean=True, ), fb.FBCommandArgument( short="-n", long="--name", arg="clsname", help="Take the argument as class name", default=False, boolean=True, ), ] def args(self): return [ fb.FBCommandArgument( arg="instance or class", type="instance or Class", help="an Objective-C Class.", ) ] def run(self, arguments, options): cls = getClassFromArgument(arguments[0], options.clsname) if options.clsmethod: print("Class Methods:") printClassMethods(cls, options.showaddr) if options.insmethod: print("\nInstance Methods:") printInstanceMethods(cls, options.showaddr) if not options.clsmethod and not options.insmethod: print("Class Methods:") printClassMethods(cls, options.showaddr) print("\nInstance Methods:") printInstanceMethods(cls, options.showaddr) class FBPrintProperties(fb.FBCommand): def name(self): return "pproperties" def description(self): return "Print the properties of an instance or Class" def options(self): return [ fb.FBCommandArgument( short="-n", long="--name", arg="clsname", help="Take the argument as class name", default=False, boolean=True, ) ] def args(self): return [ fb.FBCommandArgument( arg="instance or class", type="instance or Class", help="an Objective-C Class.", ) ] def run(self, arguments, options): cls = getClassFromArgument(arguments[0], options.clsname) printProperties(cls) class FBPrintBlock(fb.FBCommand): def name(self): return "pblock" def description(self): return "Print the block`s implementation address and signature" def args(self): return [ fb.FBCommandArgument(arg="block", help="The block object you want to print") ] def run(self, arguments, options): block = arguments[0] # http://clang.llvm.org/docs/Block-ABI-Apple.html tmpString = """ enum { BLOCK_HAS_COPY_DISPOSE = (1 << 25), BLOCK_HAS_CTOR = (1 << 26), // helpers have C++ code BLOCK_IS_GLOBAL = (1 << 28), BLOCK_HAS_STRET = (1 << 29), // IFF BLOCK_HAS_SIGNATURE BLOCK_HAS_SIGNATURE = (1 << 30), }; struct Block_literal_1 { void *isa; // initialized to &_NSConcreteStackBlock or &_NSConcreteGlobalBlock int flags; int reserved; void (*invoke)(void *, ...); struct Block_descriptor_1 { unsigned long int reserved; // NULL unsigned long int size; // sizeof(struct Block_literal_1) // optional helper functions void (*copy_helper)(void *dst, void *src); // IFF (1<<25) void (*dispose_helper)(void *src); // IFF (1<<25) // required ABI.2010.3.16 const char *signature; // IFF (1<<30) } *descriptor; // imported variables }; struct Block_literal_1 real = *((__bridge struct Block_literal_1 *)$block); NSMutableDictionary *dict = (id)[NSMutableDictionary dictionary]; [dict setObject:(id)[NSNumber numberWithLong:(long)real.invoke] forKey:@"invoke"]; if (real.flags & BLOCK_HAS_SIGNATURE) { char *signature; if (real.flags & BLOCK_HAS_COPY_DISPOSE) { signature = (char *)(real.descriptor)->signature; } else { signature = (char *)(real.descriptor)->copy_helper; } NSMethodSignature *sig = [NSMethodSignature signatureWithObjCTypes:signature]; NSMutableArray *types = [NSMutableArray array]; [types addObject:(id)[NSString stringWithUTF8String:(char *)[sig methodReturnType]]]; for (NSUInteger i = 0; i < sig.numberOfArguments; i++) { char *type = (char *)[sig getArgumentTypeAtIndex:i]; [types addObject:(id)[NSString stringWithUTF8String:type]]; } [dict setObject:types forKey:@"signature"]; } RETURN(dict); """ command = string.Template(tmpString).substitute(block=block) json = fb.evaluate(command) signature = json["signature"] if not signature: print("Imp: " + hex(json["invoke"])) return sigStr = "{} ^(".format(decode(signature[0])) # the block`s implementation always take the block as it`s first argument, so we ignore it sigStr += ", ".join([decode(m) for m in signature[2:]]) sigStr += ");" print("Imp: " + hex(json["invoke"]) + " Signature: " + sigStr) # helpers def isClassObject(arg): return runtimeHelpers.class_isMetaClass(runtimeHelpers.object_getClass(arg)) def getClassFromArgument(arg, is_classname): cls = arg if is_classname: cls = runtimeHelpers.objc_getClass(cls) if not int(cls, 16): raise Exception('Class "{}" not found'.format(arg)) else: if not isClassObject(cls): cls = runtimeHelpers.object_getClass(cls) if not isClassObject(cls): raise Exception( "Invalid argument. Please specify an instance or a Class." ) return cls def printInstanceMethods(cls, showaddr=False, prefix="-"): methods = getMethods(cls) if not methods: print("No methods were found") for m in methods: if showaddr: print(prefix + " " + m.prettyPrintString() + " " + str(m.imp)) else: print(prefix + " " + m.prettyPrintString()) def printClassMethods(cls, showaddr=False): printInstanceMethods(runtimeHelpers.object_getClass(cls), showaddr, "+") def printProperties(cls, showvalue=False): props = getProperties(cls) for p in props: print(p.prettyPrintString()) def decode(code): encodeMap = { "c": "char", "i": "int", "s": "short", "l": "long", "q": "long long", "C": "unsigned char", "I": "unsigned int", "S": "unsigned short", "L": "unsigned long", "Q": "unsigned long long", "f": "float", "d": "double", "B": "bool", "v": "void", "*": "char *", "@": "id", "#": "Class", ":": "SEL", } ret = code if code in encodeMap: ret = encodeMap[code] elif ret[0:1] == "@": if ret[1:2] == "?": # @? represent a block ret = code elif ret[2:3] == "<": # @"<aDelegate><bDelegate>" ret = "id" + ret[2:-1].replace("><", ", ") else: ret = ret[2:-1] + " *" elif ret[0:1] == "^": ret = decode(ret[1:]) + " *" return ret # Notice that evaluateExpression doesn't work with variable arguments. such as -[NSString stringWithFormat:] # I remove the "free(methods)" because it would cause evaluateExpressionValue to raise exception some time. def getMethods(klass): tmpString = """ unsigned int outCount; Method *methods = (Method *)class_copyMethodList((Class)$cls, &outCount); NSMutableArray *result = (id)[NSMutableArray array]; for (int i = 0; i < outCount; i++) { NSMutableDictionary *m = (id)[NSMutableDictionary dictionary]; SEL name = (SEL)method_getName(methods[i]); [m setObject:(id)NSStringFromSelector(name) forKey:@"name"]; char * encoding = (char *)method_getTypeEncoding(methods[i]); [m setObject:(id)[NSString stringWithUTF8String:encoding] forKey:@"type_encoding"]; NSMutableArray *types = (id)[NSMutableArray array]; NSInteger args = (NSInteger)method_getNumberOfArguments(methods[i]); for (int idx = 0; idx < args; idx++) { char *type = (char *)method_copyArgumentType(methods[i], idx); [types addObject:(id)[NSString stringWithUTF8String:type]]; } [m setObject:types forKey:@"parameters_type"]; char *ret_type = (char *)method_copyReturnType(methods[i]); [m setObject:(id)[NSString stringWithUTF8String:ret_type] forKey:@"return_type"]; long imp = (long)method_getImplementation(methods[i]); [m setObject:[NSNumber numberWithLongLong:imp] forKey:@"implementation"]; [result addObject:m]; } RETURN(result); """ command = string.Template(tmpString).substitute(cls=klass) methods = fb.evaluate(command) return [Method(m) for m in methods] class Method: def __init__(self, json): self.name = json["name"] self.type_encoding = json["type_encoding"] self.parameters_type = json["parameters_type"] self.return_type = json["return_type"] self.imp = self.toHex(json["implementation"]) def prettyPrintString(self): argnum = len(self.parameters_type) names = self.name.split(":") # the argnum count must be bigger then 2, index 0 for self, index 1 for SEL for i in range(2, argnum): arg_type = self.parameters_type[i] names[i - 2] = names[i - 2] + ":(" + decode(arg_type) + ")arg" + str(i - 2) string = " ".join(names) return "({}){}".format(decode(self.return_type), string) def toHex(self, addr): return hex(addr) def __str__(self): return ( "<Method:" + self.oc_method + "> " + self.name + " --- " + self.type + " --- " + self.imp ) def getProperties(klass): tmpString = """ NSMutableArray *result = (id)[NSMutableArray array]; unsigned int count; objc_property_t *props = (objc_property_t *)class_copyPropertyList((Class)$cls, &count); for (int i = 0; i < count; i++) { NSMutableDictionary *dict = (id)[NSMutableDictionary dictionary]; char *name = (char *)property_getName(props[i]); [dict setObject:(id)[NSString stringWithUTF8String:name] forKey:@"name"]; char *attrstr = (char *)property_getAttributes(props[i]); [dict setObject:(id)[NSString stringWithUTF8String:attrstr] forKey:@"attributes_string"]; NSMutableDictionary *attrsDict = (id)[NSMutableDictionary dictionary]; unsigned int pcount; objc_property_attribute_t *attrs = (objc_property_attribute_t *)property_copyAttributeList(props[i], &pcount); for (int i = 0; i < pcount; i++) { NSString *name = (id)[NSString stringWithUTF8String:(char *)attrs[i].name]; NSString *value = (id)[NSString stringWithUTF8String:(char *)attrs[i].value]; [attrsDict setObject:value forKey:name]; } [dict setObject:attrsDict forKey:@"attributes"]; [result addObject:dict]; } RETURN(result); """ command = string.Template(tmpString).substitute(cls=klass) propsJson = fb.evaluate(command) return [Property(m) for m in propsJson] class Property: def __init__(self, json): self.name = json["name"] self.attributes_string = json["attributes_string"] self.attributes = json["attributes"] # https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtPropertyIntrospection.html#//apple_ref/doc/uid/TP40008048-CH101-SW1 def prettyPrintString(self): attrs = [] if "N" in self.attributes: attrs.append("nonatomic") else: attrs.append("atomic") if "&" in self.attributes: attrs.append("strong") elif "C" in self.attributes: attrs.append("copy") elif "W" in self.attributes: attrs.append("weak") else: attrs.append("assign") if "R" in self.attributes: attrs.append("readonly") if "G" in self.attributes: attrs.append("getter={}".format(self.attributes["G"])) if "S" in self.attributes: attrs.append("setter={}".format(self.attributes["S"])) return "@property ({}) {} {};".format( ", ".join(attrs), decode(self.attributes["T"]), self.name )