-
Notifications
You must be signed in to change notification settings - Fork 803
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add two commands pproperties, pblock #133
Merged
Merged
Changes from all commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
0511b9a
typo fix
a440db0
merge master
b01f9bb
Add properties command
05745d3
Add document ref
be3a326
Fix a bug on FBClassDump
24eff5a
Change get_oc_methods_json to getMethods
b44c0d2
Add -n option to methods and pproperties
1ffb194
decode id and pointer
61a5d92
Support decode id<delegate>
175f96b
Add a space in print property
6a3fb35
Parser '@?' as a block in decode
1755437
Add pblock command to print the imp and signature of block
5941d5f
Merge remote-tracking branch 'facebook/master' into fb_classdump
751b8f4
Add getClassFromArgument on FBClassDump
716f10c
Fix typo on FBClassdump
8362374
Add a semicolon at the end of pproperties command output
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,7 +6,9 @@ | |
|
||
def lldbcommands(): | ||
return [ | ||
FBPrintMethods() | ||
FBPrintMethods(), | ||
FBPrintProperties(), | ||
FBPrintBlock() | ||
] | ||
|
||
class FBPrintMethods(fb.FBCommand): | ||
|
@@ -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:' | ||
|
@@ -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); | ||
|
@@ -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'] | ||
|
@@ -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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
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) |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
props
does not ever getfree
d.