-
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 pclassmethod and pinstancemethod #113
Changes from 27 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,161 @@ | ||
#!/usr/bin/python | ||
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='--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) | ||
] | ||
|
||
def args(self): | ||
return [ fb.FBCommandArgument(arg='class or instance', type='id 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.') | ||
|
||
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) | ||
|
||
def isClassObject(arg): | ||
return runtimeHelpers.class_isMetaClass(runtimeHelpers.object_getClass(arg)) | ||
|
||
def printInstanceMethods(cls, showaddr=False, prefix='-'): | ||
json_method_array = get_oc_methods_json(cls) | ||
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() | ||
|
||
def printClassMethods(cls, showaddr=False): | ||
printInstanceMethods(runtimeHelpers.object_getClass(cls), showaddr, '+') | ||
|
||
# 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): | ||
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) | ||
return fb.evaluate(command) | ||
|
||
|
||
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'] | ||
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] + ":(" + self.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 | ||
|
||
def toHex(self, addr): | ||
return hex(addr) | ||
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 method looks superfluous. |
||
|
||
def __str__(self): | ||
return "<Method:" + self.oc_method + "> " + self.name + " --- " + self.type + " --- " + self.imp |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,6 +8,7 @@ | |
# of patent rights can be found in the PATENTS file in the same directory. | ||
|
||
import lldb | ||
import json | ||
|
||
class FBCommandArgument: | ||
def __init__(self, short='', long='', arg='', type='', help='', default='', boolean=False): | ||
|
@@ -73,3 +74,63 @@ def evaluateExpression(expression, printErrors=True): | |
|
||
def evaluateObjectExpression(expression, printErrors=True): | ||
return evaluateExpression('(id)(' + expression + ')', printErrors) | ||
|
||
def evaluateCStringExpression(expression, printErrors=True): | ||
ret = evaluateExpression(expression, printErrors) | ||
|
||
process = lldb.debugger.GetSelectedTarget().GetProcess() | ||
error = lldb.SBError() | ||
ret = process.ReadCStringFromMemory(int(ret, 16), 256, error) | ||
if error.Success(): | ||
return ret | ||
else: | ||
if printErrors: | ||
print error | ||
return None | ||
|
||
|
||
RETURN_MACRO = """ | ||
#define IS_JSON_OBJ(obj)\ | ||
(obj != nil && ((bool)[NSJSONSerialization isValidJSONObject:obj] ||\ | ||
(bool)[obj isKindOfClass:[NSString class]] ||\ | ||
(bool)[obj isKindOfClass:[NSNumber class]])) | ||
#define RETURN(ret) ({\ | ||
if (!IS_JSON_OBJ(ret)) {\ | ||
(void)[NSException raise:@"Invalied RETRUN argument" format:@""];\ | ||
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 RETURN argument 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 This is still in need of fixing. |
||
}\ | ||
NSDictionary *__dict = @{@"return":ret};\ | ||
NSData *__data = (id)[NSJSONSerialization dataWithJSONObject:__dict options:0 error:NULL];\ | ||
NSString *__str = (id)[[NSString alloc] initWithData:__data encoding:4];\ | ||
(char *)[__str UTF8String];}) | ||
#define RETURNCString(ret)\ | ||
({NSString *___cstring_ret = [NSString stringWithUTF8String:ret];\ | ||
RETURN(___cstring_ret);}) | ||
""" | ||
|
||
def check_expr(expr): | ||
return expr.strip().split(';')[-2].find('RETURN') != -1 | ||
|
||
# evaluate a batch of Objective-C expressions, the last expression must contain a RETURN marco | ||
# and it will automatic transform the Objective-C object to Python object | ||
# Example: | ||
# >>> fblldbbase.evaluate('NSString *str = @"hello world"; RETURN(@{@"key": str});') | ||
# {u'key': u'hello world'} | ||
def evaluate(expr): | ||
if not check_expr(expr): | ||
raise Exception("Invalid Expression, the last expression not include a RETURN family marco") | ||
|
||
command = "({" + RETURN_MACRO + '\n' + expr + "})" | ||
ret = evaluateExpressionValue(command, True) | ||
if not ret.GetError().Success(): | ||
print ret.GetError() | ||
return None | ||
else: | ||
process = lldb.debugger.GetSelectedTarget().GetProcess() | ||
error = lldb.SBError() | ||
ret = process.ReadCStringFromMemory(int(ret.GetValue(), 16), 2**20, error) | ||
if not error.Success(): | ||
print error | ||
return None | ||
else: | ||
ret = json.loads(ret) | ||
return ret['return'] |
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) | ||
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?