Skip to content

Commit

Permalink
Merge pull request #113 from longv2go/master
Browse files Browse the repository at this point in the history
add two commands pclassmethod and  pinstancemethod
  • Loading branch information
kastiglione committed Jan 1, 2016
2 parents 8f6ed96 + 4f2f0ec commit 867b4e2
Show file tree
Hide file tree
Showing 3 changed files with 229 additions and 0 deletions.
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)

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)

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

0 comments on commit 867b4e2

Please sign in to comment.