Skip to content

Commit 867b4e2

Browse files
committed
Merge pull request #113 from longv2go/master
add two commands pclassmethod and pinstancemethod
2 parents 8f6ed96 + 4f2f0ec commit 867b4e2

File tree

3 files changed

+229
-0
lines changed

3 files changed

+229
-0
lines changed

commands/FBClassDump.py

+164
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
#!/usr/bin/python
2+
import string
3+
import lldb
4+
import fblldbbase as fb
5+
import fblldbobjcruntimehelpers as runtimeHelpers
6+
7+
def lldbcommands():
8+
return [
9+
FBPrintMethods()
10+
]
11+
12+
class FBPrintMethods(fb.FBCommand):
13+
def name(self):
14+
return 'pmethods'
15+
16+
def description(self):
17+
return 'Print the class and instance methods of a class.'
18+
19+
def options(self):
20+
return [
21+
fb.FBCommandArgument(short='-a', long='--address', arg='showaddr', help='Print the implementation address of the method', default=False, boolean=True),
22+
fb.FBCommandArgument(short='-i', long='--instance', arg='insmethod', help='Print the instance methods', default=False, boolean=True),
23+
fb.FBCommandArgument(short='-c', long='--class', arg='clsmethod', help='Print the class methods', default=False, boolean=True)
24+
]
25+
26+
def args(self):
27+
return [ fb.FBCommandArgument(arg='class or instance', type='instance or Class', help='an Objective-C Class.') ]
28+
29+
def run(self, arguments, options):
30+
cls = arguments[0]
31+
if not isClassObject(cls):
32+
cls = runtimeHelpers.object_getClass(cls)
33+
if not isClassObject(cls):
34+
raise Exception('Invalid argument. Please specify an instance or a Class.')
35+
36+
if options.clsmethod:
37+
print 'Class Methods:'
38+
printClassMethods(cls, options.showaddr)
39+
40+
if options.insmethod:
41+
print '\nInstance Methods:'
42+
printInstanceMethods(cls, options.showaddr)
43+
44+
if not options.clsmethod and not options.insmethod:
45+
print 'Class Methods:'
46+
printClassMethods(cls, options.showaddr)
47+
print '\nInstance Methods:'
48+
printInstanceMethods(cls, options.showaddr)
49+
50+
def isClassObject(arg):
51+
return runtimeHelpers.class_isMetaClass(runtimeHelpers.object_getClass(arg))
52+
53+
def printInstanceMethods(cls, showaddr=False, prefix='-'):
54+
json_method_array = get_oc_methods_json(cls)
55+
if not json_method_array:
56+
print "No methods were found"
57+
58+
if json_method_array:
59+
for m in json_method_array:
60+
method = Method(m)
61+
62+
if showaddr:
63+
print prefix + ' ' + method.prettyPrintString() + ' ' + str(method.imp)
64+
else:
65+
print prefix + ' ' + method.prettyPrintString()
66+
67+
def printClassMethods(cls, showaddr=False):
68+
printInstanceMethods(runtimeHelpers.object_getClass(cls), showaddr, '+')
69+
70+
# Notice that evaluateExpression doesn't work with variable arguments. such as -[NSString stringWithFormat:]
71+
# I remove the "free(methods)" because it would cause evaluateExpressionValue to raise exception some time.
72+
def get_oc_methods_json(klass):
73+
tmpString = """
74+
unsigned int outCount;
75+
Method *methods = (Method *)class_copyMethodList((Class)$cls, &outCount);
76+
NSMutableArray *result = (id)[NSMutableArray array];
77+
78+
for (int i = 0; i < outCount; i++) {
79+
NSMutableDictionary *m = (id)[NSMutableDictionary dictionary];
80+
81+
SEL name = (SEL)method_getName(methods[i]);
82+
[m setObject:(id)NSStringFromSelector(name) forKey:@"name"];
83+
84+
char * encoding = (char *)method_getTypeEncoding(methods[i]);
85+
[m setObject:(id)[NSString stringWithUTF8String:encoding] forKey:@"type_encoding"];
86+
87+
NSMutableArray *types = (id)[NSMutableArray array];
88+
NSInteger args = (NSInteger)method_getNumberOfArguments(methods[i]);
89+
for (int idx = 0; idx < args; idx++) {
90+
char *type = (char *)method_copyArgumentType(methods[i], idx);
91+
[types addObject:(id)[NSString stringWithUTF8String:type]];
92+
}
93+
[m setObject:types forKey:@"parameters_type"];
94+
95+
char *ret_type = (char *)method_copyReturnType(methods[i]);
96+
[m setObject:(id)[NSString stringWithUTF8String:ret_type] forKey:@"return_type"];
97+
98+
long imp = (long)method_getImplementation(methods[i]);
99+
[m setObject:[NSNumber numberWithLongLong:imp] forKey:@"implementation"];
100+
101+
[result addObject:m];
102+
}
103+
RETURN(result);
104+
"""
105+
command = string.Template(tmpString).substitute(cls=klass)
106+
return fb.evaluate(command)
107+
108+
109+
class Method:
110+
111+
encodeMap = {
112+
'c': 'char',
113+
'i': 'int',
114+
's': 'short',
115+
'l': 'long',
116+
'q': 'long long',
117+
118+
'C': 'unsigned char',
119+
'I': 'unsigned int',
120+
'S': 'unsigned short',
121+
'L': 'unsigned long',
122+
'Q': 'unsigned long long',
123+
124+
'f': 'float',
125+
'd': 'double',
126+
'B': 'bool',
127+
'v': 'void',
128+
'*': 'char *',
129+
'@': 'id',
130+
'#': 'Class',
131+
':': 'SEL',
132+
}
133+
134+
def __init__(self, json):
135+
self.name = json['name']
136+
self.type_encoding = json['type_encoding']
137+
self.parameters_type = json['parameters_type']
138+
self.return_type = json['return_type']
139+
self.imp = self.toHex(json['implementation'])
140+
141+
def prettyPrintString(self):
142+
argnum = len(self.parameters_type)
143+
names = self.name.split(':')
144+
145+
# the argnum count must be bigger then 2, index 0 for self, index 1 for SEL
146+
for i in range(2, argnum):
147+
arg_type = self.parameters_type[i]
148+
names[i-2] = names[i-2] + ":(" + self.decode(arg_type) + ")arg" + str(i-2)
149+
150+
string = " ".join(names)
151+
return "({}){}".format(self.decode(self.return_type), string)
152+
153+
154+
def decode(self, type):
155+
ret = type
156+
if type in Method.encodeMap:
157+
ret = Method.encodeMap[type]
158+
return ret
159+
160+
def toHex(self, addr):
161+
return hex(addr)
162+
163+
def __str__(self):
164+
return "<Method:" + self.oc_method + "> " + self.name + " --- " + self.type + " --- " + self.imp

fblldbbase.py

+61
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
# of patent rights can be found in the PATENTS file in the same directory.
99

1010
import lldb
11+
import json
1112

1213
class FBCommandArgument:
1314
def __init__(self, short='', long='', arg='', type='', help='', default='', boolean=False):
@@ -73,3 +74,63 @@ def evaluateExpression(expression, printErrors=True):
7374

7475
def evaluateObjectExpression(expression, printErrors=True):
7576
return evaluateExpression('(id)(' + expression + ')', printErrors)
77+
78+
def evaluateCStringExpression(expression, printErrors=True):
79+
ret = evaluateExpression(expression, printErrors)
80+
81+
process = lldb.debugger.GetSelectedTarget().GetProcess()
82+
error = lldb.SBError()
83+
ret = process.ReadCStringFromMemory(int(ret, 16), 256, error)
84+
if error.Success():
85+
return ret
86+
else:
87+
if printErrors:
88+
print error
89+
return None
90+
91+
92+
RETURN_MACRO = """
93+
#define IS_JSON_OBJ(obj)\
94+
(obj != nil && ((bool)[NSJSONSerialization isValidJSONObject:obj] ||\
95+
(bool)[obj isKindOfClass:[NSString class]] ||\
96+
(bool)[obj isKindOfClass:[NSNumber class]]))
97+
#define RETURN(ret) ({\
98+
if (!IS_JSON_OBJ(ret)) {\
99+
(void)[NSException raise:@"Invalid RETURN argument" format:@""];\
100+
}\
101+
NSDictionary *__dict = @{@"return":ret};\
102+
NSData *__data = (id)[NSJSONSerialization dataWithJSONObject:__dict options:0 error:NULL];\
103+
NSString *__str = (id)[[NSString alloc] initWithData:__data encoding:4];\
104+
(char *)[__str UTF8String];})
105+
#define RETURNCString(ret)\
106+
({NSString *___cstring_ret = [NSString stringWithUTF8String:ret];\
107+
RETURN(___cstring_ret);})
108+
"""
109+
110+
def check_expr(expr):
111+
return expr.strip().split(';')[-2].find('RETURN') != -1
112+
113+
# evaluate a batch of Objective-C expressions, the last expression must contain a RETURN marco
114+
# and it will automatic transform the Objective-C object to Python object
115+
# Example:
116+
# >>> fblldbbase.evaluate('NSString *str = @"hello world"; RETURN(@{@"key": str});')
117+
# {u'key': u'hello world'}
118+
def evaluate(expr):
119+
if not check_expr(expr):
120+
raise Exception("Invalid Expression, the last expression not include a RETURN family marco")
121+
122+
command = "({" + RETURN_MACRO + '\n' + expr + "})"
123+
ret = evaluateExpressionValue(command, True)
124+
if not ret.GetError().Success():
125+
print ret.GetError()
126+
return None
127+
else:
128+
process = lldb.debugger.GetSelectedTarget().GetProcess()
129+
error = lldb.SBError()
130+
ret = process.ReadCStringFromMemory(int(ret.GetValue(), 16), 2**20, error)
131+
if not error.Success():
132+
print error
133+
return None
134+
else:
135+
ret = json.loads(ret)
136+
return ret['return']

fblldbobjcruntimehelpers.py

+4
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ def class_getSuperclass(klass):
3232
value = fb.evaluateExpression(command)
3333
return value
3434

35+
def class_isMetaClass(klass):
36+
command = 'class_isMetaClass((Class){})'.format(klass)
37+
return fb.evaluateBooleanExpression(command)
38+
3539
def class_getInstanceMethod(klass, selector):
3640
command = '(void*)class_getInstanceMethod((Class){}, @selector({}))'.format(klass, selector)
3741
value = fb.evaluateExpression(command)

0 commit comments

Comments
 (0)