Skip to content
Merged
Show file tree
Hide file tree
Changes from 14 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
190 changes: 190 additions & 0 deletions commands/FBClassDump.py
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.'
Copy link
Copy Markdown

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."

Copy link
Copy Markdown
Contributor

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?


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),
Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good idea!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The 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.') ]
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

"Object-C" should be "Objective-C"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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')
Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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))
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

There's simple object_isClass method in objc_runtime
No need to tweak around with metaclasses

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The 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-- "
Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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:]
Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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):
Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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
Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The 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
Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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)
13 changes: 13 additions & 0 deletions fblldbbase.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,16 @@ 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
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 = '(BOOL)class_isMetaClass((Class){})'.format(klass)
return fb.evaluateBooleanExpression(command)
Copy link
Copy Markdown
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