-
Notifications
You must be signed in to change notification settings - Fork 792
add two commands pclassmethod and pinstancemethod #113
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
Changes from 14 commits
52c8500
44477a9
bfc1bcd
01cd621
5e46ca7
48beada
d59b1d3
c910d48
cab9666
37f5a96
34a5152
c701083
57a4055
df9db7c
05a0671
1799ee0
1967de6
b59e8fa
f967b1d
31b6e2d
c5b33d1
3b39c3c
c4870d5
3299b90
2fba2df
695049b
a0c0540
3154bb3
c6eb2b2
4f2f0ec
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,190 @@ | ||
| #!/usr/bin/python | ||
|
|
||
| import os | ||
| import re | ||
| import string | ||
|
|
||
| import lldb | ||
| import fblldbbase as fb | ||
| import fblldbobjcruntimehelpers as runtimeHelpers | ||
|
|
||
| def lldbcommands(): | ||
| return [ | ||
| FBPrintMethods() | ||
| ] | ||
|
|
||
| class FBPrintMethods(fb.FBCommand): | ||
| def name(self): | ||
| return 'pmethods' | ||
|
|
||
| def description(self): | ||
| return 'Print the class instance methods.' | ||
|
|
||
| def options(self): | ||
| return [ | ||
| fb.FBCommandArgument(short='-a', long='--all', arg='all', help='If display all methos include class and instance methods', default=False, boolean=True), | ||
|
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. I find the combination of '-a' and '-c' to be a bit counter-intuitive. What about '-i/--instance' for instance and '-c/--class' for class and if neither is specified then it defaults to both. This ensures that there aren't any combinations that don't make sense.
Contributor
Author
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. Good idea!
Contributor
Author
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. I just add a new '-a' option to show the implementation address of the method |
||
| fb.FBCommandArgument(short='-c', long='--class', arg='clsmethod', help='Print the class methods only', default=False, boolean=True) | ||
| ] | ||
|
|
||
| def args(self): | ||
| return [ fb.FBCommandArgument(arg='class or instance', type='id or Class', help='an Object-C Class.') ] | ||
|
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. "Object-C" should be "Objective-C" 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. "id or Class" should be "instance or Class." |
||
|
|
||
| def run(self, arguments, options): | ||
| cls = arguments[0] | ||
| if not isClassObject(cls): | ||
| cls = runtimeHelpers.object_getClass(cls) | ||
| if not isClassObject(cls): | ||
| raise Exception('parameter invalide, not a id or Class') | ||
|
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. "Invalid argument. Please specify an instance or a Class." |
||
|
|
||
| if options.all: | ||
| printClassMethods(cls) | ||
| printInstanceMethods(cls) | ||
| elif options.clsmethod: | ||
| printClassMethods(cls) | ||
| else: | ||
| printInstanceMethods(cls) | ||
|
|
||
| def isClassObject(arg): | ||
| return runtimeHelpers.class_isMetaClass(runtimeHelpers.object_getClass(arg)) | ||
|
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. There's simple
Contributor
Author
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. It support since iOS 8 |
||
|
|
||
| def printInstanceMethods(cls, prefix='-'): | ||
| ocarray = instanceMethosOfClass(cls) | ||
| if not ocarray: | ||
| print "-- have none method or an error occur-- " | ||
|
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. How about: "No instance methods were found." |
||
| return | ||
|
|
||
| methodAddrs = covertOCArrayToPyArray(ocarray) | ||
| methods = [] | ||
| for i in methodAddrs: | ||
| method = createMethodFromOCMethod(i) | ||
| if method is not None: | ||
| methods.append(method) | ||
| print prefix + ' ' + method.prettyPrint() | ||
|
|
||
| def printClassMethods(cls): | ||
| printInstanceMethods(runtimeHelpers.object_getClass(cls), '+') | ||
|
|
||
| # I find that a method that has variable parameter can not b.evaluateExpression | ||
| # so I use numberWithLongLong: rather than -[NSString stringWithFormat:] | ||
|
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. This is correct. Can you change the comment to be a bit more assertive? Maybe "Use numberWithLongLong: rather than -[NSString stringWithFormat:] since evaluateExpression doesn't work with variable arguments." |
||
| def instanceMethosOfClass(klass): | ||
|
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. Typo s/Methos/Methods/ |
||
| tmpString = """ | ||
| unsigned int outCount; | ||
| void **methods = (void **)class_copyMethodList((Class)$cls, &outCount); | ||
| NSMutableArray *result = [NSMutableArray array]; | ||
| for (int i = 0; i < outCount; i++) { | ||
| NSNumber *num = (NSNumber *)[NSNumber numberWithLongLong:(long long)methods[i]]; | ||
| [result addObject:num]; | ||
| } | ||
| (void)free(methods); | ||
| id ret = result.count ? [result copy] : nil; | ||
| ret; | ||
| """ | ||
|
|
||
| command = string.Template(tmpString).substitute(cls=klass) | ||
| command = '({' + command + '})' | ||
| ret = fb.evaluateExpressionValue(command) | ||
| if not ret.GetError().Success(): | ||
| return None | ||
|
|
||
| ret = ret.GetValue() | ||
| if int(ret, 16) == 0: # return nil | ||
|
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. This could be removed if you use fb.evaluateIntegerExpressionValue a few lines above.
Contributor
Author
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. the fb.evaluateIntegerExpressionValue would round the express with "(int)( expr )", this can cause error when the expr is "({ ... })" |
||
| ret = None | ||
| return ret | ||
|
|
||
| # OC array only can hold id, | ||
| # @return an array whose instance type is str of the oc object`s address | ||
|
|
||
| def covertOCArrayToPyArray(oc_array): | ||
| is_array = fb.evaluateBooleanExpression("[{} isKindOfClass:[NSArray class]]".format(oc_array)) | ||
| if not is_array: | ||
| return None | ||
|
|
||
| result = [] | ||
| count = fb.evaluateExpression("(int)[{} count]".format(oc_array)) | ||
|
|
||
| for i in range(int(count)): | ||
| value = fb.evaluateExpression("(id)[{} objectAtIndex:{}]".format(oc_array, i)) | ||
| value = fb.evaluateExpression("(long long)[{} longLongValue]".format(value)) | ||
| result.append(value) | ||
|
|
||
| return result | ||
|
|
||
|
|
||
| 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, name, type_encoding, imp, oc_method): | ||
| self.name = name | ||
| self.type = type_encoding | ||
| self.imp = imp | ||
| self.oc_method = self.toHex(oc_method) | ||
|
|
||
| def prettyPrint(self): | ||
| # mast be bigger then 2, 0-idx for self, 1-st for SEL | ||
|
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. s/mast/must I don't quite understand this comment, especially the "1-st" part. |
||
| argnum = fb.evaluateIntegerExpression("method_getNumberOfArguments({})".format(self.oc_method)) | ||
| names = self.name.split(':') | ||
|
|
||
| for i in range(2, argnum): | ||
| arg_type = fb.evaluateCStringExpression("(char *)method_copyArgumentType({}, {})".format(self.oc_method, i)) | ||
| names[i-2] = names[i-2] + ":(" + self.decode(arg_type) + ")arg" + str(i-2) | ||
|
|
||
| string = " ".join(names) | ||
|
|
||
| ret_type = fb.evaluateCStringExpression("(char *)method_copyReturnType({})".format(self.oc_method)) | ||
| return "({}){}".format(self.decode(ret_type), string) | ||
|
|
||
|
|
||
| def decode(self, type): | ||
| ret = type | ||
| if type in Method.encodeMap: | ||
| ret = Method.encodeMap[type] | ||
| return ret | ||
|
|
||
| def toHex(self, addr): | ||
| return addr | ||
|
|
||
| def __str__(self): | ||
| return "<Method:" + self.oc_method + "> " + self.name + " --- " + self.type + " --- " + self.imp | ||
|
|
||
| def createMethodFromOCMethod(method): | ||
| process = lldb.debugger.GetSelectedTarget().GetProcess() | ||
| error = lldb.SBError() | ||
|
|
||
| nameValue = fb.evaluateExpression("(char *)method_getName({})".format(method)) | ||
| name = process.ReadCStringFromMemory(int(nameValue, 16), 256, error) | ||
|
|
||
| if not error.Success(): | ||
| print error | ||
| return None | ||
|
|
||
| typeEncodingValue = fb.evaluateExpression("(char *)method_getTypeEncoding({})".format(method)) | ||
| type_encoding = process.ReadCStringFromMemory(int(typeEncodingValue, 16), 256, error) | ||
|
|
||
| if not error.Success(): | ||
| print error | ||
| return None | ||
|
|
||
| imp = fb.evaluateExpression("(void *)method_getImplementation({})".format(method)) | ||
| return Method(name, type_encoding, imp, method) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -32,6 +32,10 @@ def class_getSuperclass(klass): | |
| value = fb.evaluateExpression(command) | ||
| return value | ||
|
|
||
| def class_isMetaClass(klass): | ||
| command = '(BOOL)class_isMetaClass((Class){})'.format(klass) | ||
| return fb.evaluateBooleanExpression(command) | ||
|
Contributor
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. @longv2go You don't need the |
||
|
|
||
| def class_getInstanceMethod(klass, selector): | ||
| command = '(void*)class_getInstanceMethod((Class){}, @selector({}))'.format(klass, selector) | ||
| value = fb.evaluateExpression(command) | ||
|
|
||
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.
"Print the class and instance methods of a class."
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.
@longv2go Do you plan to update this description?