Skip to content
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

Merged
merged 30 commits into from
Jan 1, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
52c8500
Merge pull request #1 from facebook/master
longv2go Jan 28, 2015
44477a9
Merge pull request #2 from facebook/master
longv2go Mar 4, 2015
bfc1bcd
Merge pull request #3 from facebook/master
longv2go Apr 12, 2015
01cd621
Merge pull request #4 from facebook/master
longv2go Aug 31, 2015
5e46ca7
Merge pull request #5 from facebook/master
longv2go Sep 16, 2015
48beada
add class dump
Sep 16, 2015
d59b1d3
fix
Sep 16, 2015
c910d48
add pclassmethods command
Sep 17, 2015
cab9666
change error log
Sep 17, 2015
37f5a96
handle error
Sep 17, 2015
34a5152
change FBPrintClassInstanceMethods command name
Sep 17, 2015
c701083
fix bug
Sep 17, 2015
57a4055
combine the pclassmethods and pinstancemehtods
Sep 29, 2015
df9db7c
pmethods can take an instance para
Sep 29, 2015
05a0671
add new option '-a'
Dec 14, 2015
1799ee0
Fix an issue to cause lldb run failed
Dec 15, 2015
1967de6
add comment
Dec 15, 2015
b59e8fa
change the impletation of pmethods with json
Dec 15, 2015
f967b1d
Merge remote-tracking branch 'facebook/master' into fb_classdump
Dec 15, 2015
31b6e2d
rename the eval to evaluate and move it to fblldbbase
Dec 16, 2015
c5b33d1
fix typo mistake
Dec 16, 2015
3b39c3c
Merge remote-tracking branch 'facebook/master'
Dec 16, 2015
c4870d5
Merge branch 'fb_classdump'
Dec 16, 2015
3299b90
fix typo error
Dec 16, 2015
2fba2df
Remove no need import
Dec 16, 2015
695049b
Fix typo in FBClassDump.py
Dec 16, 2015
a0c0540
Fix bug in check_expr
Dec 16, 2015
3154bb3
Remove unnessary cast in class_isMetaClass
Dec 18, 2015
c6eb2b2
Fix typo on fblldbbase.py
Dec 18, 2015
4f2f0ec
Fix description of pmethods
Dec 18, 2015
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
164 changes: 164 additions & 0 deletions commands/FBClassDump.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
#!/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 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)
]

def args(self):
return [ fb.FBCommandArgument(arg='class or instance', 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.')

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 not json_method_array:
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()

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)
Copy link
Contributor

Choose a reason for hiding this comment

The 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
61 changes: 61 additions & 0 deletions fblldbbase.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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:@"Invalid RETURN argument" format:@""];\
}\
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']
4 changes: 4 additions & 0 deletions fblldbobjcruntimehelpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ def class_getSuperclass(klass):
value = fb.evaluateExpression(command)
return value

def class_isMetaClass(klass):
command = 'class_isMetaClass((Class){})'.format(klass)
return fb.evaluateBooleanExpression(command)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@longv2go You don't need the (BOOL) cast when calling evaluateBooleanExpression.


def class_getInstanceMethod(klass, selector):
command = '(void*)class_getInstanceMethod((Class){}, @selector({}))'.format(klass, selector)
value = fb.evaluateExpression(command)
Expand Down