Skip to content

Commit

Permalink
Merge pull request #133 from longv2go/fb_classdump
Browse files Browse the repository at this point in the history
Add two commands pproperties, pblock
  • Loading branch information
kastiglione committed May 9, 2016
2 parents 381dff8 + 8362374 commit 0fe1d9c
Showing 1 changed file with 249 additions and 53 deletions.
302 changes: 249 additions & 53 deletions commands/FBClassDump.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@

def lldbcommands():
return [
FBPrintMethods()
FBPrintMethods(),
FBPrintProperties(),
FBPrintBlock()
]

class FBPrintMethods(fb.FBCommand):
Expand All @@ -20,18 +22,15 @@ 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='-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='class or instance', type='instance or Class', help='an Objective-C Class.') ]
return [ fb.FBCommandArgument(arg='instance or class', type='instance or Class', help='an Objective-C Class.') ]

def run(self, arguments, options):
cls = arguments[0]
if not isClassObject(cls):
cls = runtimeHelpers.object_getClass(cls)
if not isClassObject(cls):
raise Exception('Invalid argument. Please specify an instance or a Class.')
cls = getClassFromArgument(arguments[0], options.clsname)

if options.clsmethod:
print 'Class Methods:'
Expand All @@ -47,29 +46,190 @@ def run(self, arguments, options):
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='-'):
json_method_array = get_oc_methods_json(cls)
if not json_method_array:
methods = getMethods(cls)
if not methods:
print "No methods were found"

if json_method_array:
for m in json_method_array:
method = Method(m)

if showaddr:
print prefix + ' ' + method.prettyPrintString() + ' ' + str(method.imp)
else:
print prefix + ' ' + method.prettyPrintString()
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 get_oc_methods_json(klass):
def getMethods(klass):
tmpString = """
unsigned int outCount;
Method *methods = (Method *)class_copyMethodList((Class)$cls, &outCount);
Expand Down Expand Up @@ -103,34 +263,11 @@ def get_oc_methods_json(klass):
RETURN(result);
"""
command = string.Template(tmpString).substitute(cls=klass)
return fb.evaluate(command)

methods = fb.evaluate(command)
return [Method(m) for m in methods]

class Method:

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',
}

def __init__(self, json):
self.name = json['name']
self.type_encoding = json['type_encoding']
Expand All @@ -145,20 +282,79 @@ def prettyPrintString(self):
# 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] + ":(" + self.decode(arg_type) + ")arg" + str(i-2)
names[i-2] = names[i-2] + ":(" + decode(arg_type) + ")arg" + str(i-2)

string = " ".join(names)
return "({}){}".format(self.decode(self.return_type), string)


def decode(self, type):
ret = type
if type in Method.encodeMap:
ret = Method.encodeMap[type]
return ret
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 self.attributes.has_key('N'):
attrs.append('nonatomic')
else:
attrs.append('atomic')

if self.attributes.has_key('&'):
attrs.append('strong')
elif self.attributes.has_key('C'):
attrs.append('copy')
elif self.attributes.has_key('W'):
attrs.append('weak')
else:
attrs.append('assign')

if self.attributes.has_key('R'):
attrs.append('readonly')

if self.attributes.has_key('G'):
attrs.append("getter={}".format(self.attributes['G']))
if self.attributes.has_key('S'):
attrs.append("setter={}".format(self.attributes['S']))

return "@property ({}) {} {};".format(", ".join(attrs), decode(self.attributes['T']), self.name)

0 comments on commit 0fe1d9c

Please sign in to comment.