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 print capture variables for pblock #242

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
117 changes: 108 additions & 9 deletions commands/FBClassDump.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ def run(self, arguments, options):
struct Block_literal_1 real = *((__bridge struct Block_literal_1 *)$block);
NSMutableDictionary *dict = (id)[NSMutableDictionary dictionary];

[dict setObject:(id)[NSNumber numberWithLong:(long)real.invoke] forKey:@"invoke"];
[dict setValue:(id)[NSNumber numberWithLong:(long)real.invoke] forKey:@"invoke"];
Copy link
Contributor

Choose a reason for hiding this comment

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

why the change to setValue:forKey:?

Copy link
Author

Choose a reason for hiding this comment

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

That can fix #198 . There is another MR that can fix it separately

#241

I guess because function declaration of setValue:forKey doesn't need id , just id


if (real.flags & BLOCK_HAS_SIGNATURE) {
char *signature;
Expand All @@ -131,25 +131,124 @@ def run(self, arguments, options):
[types addObject:(id)[NSString stringWithUTF8String:type]];
}

[dict setObject:types forKey:@"signature"];
[dict setValue:types forKey:@"signature"];
}

RETURN(dict);
"""
command = string.Template(tmpString).substitute(block=block)
json = fb.evaluate(command)

# We assume that the maximum number of variables captured by the block is 10
max_var_count = 10
variables_json = self.getBlockVariables(block, max_var_count)
if variables_json is not None:
json.update(variables_json)

variablesStrs = []
for i in range(max_var_count):
varKey = 'variables['+str(i)+']'
if varKey in json:
variablesStrs.append(json[varKey])
variablesStr = '\n'.join(variablesStrs)

signature = json['signature']
if not signature:
print 'Imp: ' + hex(json['invoke'])
print 'Imp: ' + hex(json['invoke']) + ' Variables : {\n'+variablesStr+'\n};'
return

sigStr = '{} ^('.format(decode(signature[0]))
# the block`s implementation always take the block as it`s first argument, so we ignore it
sigStr += ', '.join([decode(m) for m in signature[2:]])
sigStr += ');'
sigStr += ')'

print 'Imp: ' + hex(json['invoke']) + ' Signature: ' + sigStr
print 'Imp: ' + hex(json['invoke']) + ' Signature: ' + sigStr + ' Variables : {\n'+variablesStr+'\n};'

def getBlockVariables(self, block, max_var_count):
'''
no __Block_byref_xxx
We must check the block's captured variables one by one here.
no reason, but it works.
We assume that the maximum number of variables captured by the block is max_var_count
'''

# http://clang.llvm.org/docs/Block-ABI-Apple.html
tmpString = """
#define BLOCK_VARIABLES_COUNT ($variables_count)
enum {
BLOCK_HAS_COPY_DISPOSE = (1 << 25),
BLOCK_HAS_CTOR = (1 << 26), // helpers have C++ code
BLOCK_IS_GLOBAL = (1 << 28),
BLOCK_HAS_STRET = (1 << 29), // IFF BLOCK_HAS_SIGNATURE
BLOCK_HAS_SIGNATURE = (1 << 30),
};
struct Block_literal_1 {
void *isa; // initialized to &_NSConcreteStackBlock or &_NSConcreteGlobalBlock
int flags;
int reserved;
void (*invoke)(void *, ...);
struct Block_descriptor_1 {
unsigned long int reserved; // NULL
unsigned long int size; // sizeof(struct Block_literal_1)
// optional helper functions
void (*copy_helper)(void *dst, void *src); // IFF (1<<25)
void (*dispose_helper)(void *src); // IFF (1<<25)
// required ABI.2010.3.16
const char *signature; // IFF (1<<30)
} *descriptor;
// imported variables
Class *variables[BLOCK_VARIABLES_COUNT];
};
struct Block_literal_1 real = *((__bridge struct Block_literal_1 *)$block);
NSMutableDictionary *dict = (id)[NSMutableDictionary dictionary];

// Get the list of classes and look for testPointerClass
NSInteger numClasses = (NSInteger)objc_getClassList(NULL, 0);
Class *classesList = (Class*)malloc(sizeof(Class) * numClasses);
numClasses = (NSInteger)objc_getClassList(classesList, numClasses);

Class **blockVariables = real.variables;
for (int i = 0; i < BLOCK_VARIABLES_COUNT; i++) {
Class *obj = (Class*)blockVariables[i];
if (obj == NULL) {
break;
}

Class testPointerClass = (Class)(*obj);
BOOL isClass = NO;
for (int i = 0; i < numClasses; i++)
{
if (classesList[i] == testPointerClass)
{
isClass = YES;
break;
}
}
// __Block_byref_xxx may break this
/*
if (!isClass) {
break;
}
*/

NSString *key = [NSString stringWithFormat:@"variables[%d]", i];
NSString *value = [NSString stringWithFormat:@"%@", obj];
[dict setValue:value forKey:key];
}

free(classesList);

RETURN(dict);
"""
last_json = None
for i in range(1, max_var_count):
command = string.Template(tmpString).substitute(block=block, variables_count=i)
json = fb.evaluate(command, printErrors=False)
if json is not None:
last_json = json
else:
break
return last_json

# helpers
def isClassObject(arg):
Expand Down Expand Up @@ -302,20 +401,20 @@ def getProperties(klass):
NSMutableDictionary *dict = (id)[NSMutableDictionary dictionary];

char *name = (char *)property_getName(props[i]);
[dict setObject:(id)[NSString stringWithUTF8String:name] forKey:@"name"];
[dict setValue:(id)[NSString stringWithUTF8String:name] forKey:@"name"];

char *attrstr = (char *)property_getAttributes(props[i]);
[dict setObject:(id)[NSString stringWithUTF8String:attrstr] forKey:@"attributes_string"];
[dict setValue:(id)[NSString stringWithUTF8String:attrstr] forKey:@"attributes_string"];

NSMutableDictionary *attrsDict = (id)[NSMutableDictionary dictionary];
unsigned int pcount;
objc_property_attribute_t *attrs = (objc_property_attribute_t *)property_copyAttributeList(props[i], &pcount);
for (int i = 0; i < pcount; i++) {
NSString *name = (id)[NSString stringWithUTF8String:(char *)attrs[i].name];
NSString *value = (id)[NSString stringWithUTF8String:(char *)attrs[i].value];
[attrsDict setObject:value forKey:name];
[attrsDict setValue:value forKey:name];
}
[dict setObject:attrsDict forKey:@"attributes"];
[dict setValue:attrsDict forKey:@"attributes"];

[result addObject:dict];
}
Expand Down
17 changes: 10 additions & 7 deletions fblldbbase.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,13 +144,14 @@ def evaluateCStringExpression(expression, printErrors=True):
RETURN_MACRO = """
#define IS_JSON_OBJ(obj)\
(obj != nil && ((bool)[NSJSONSerialization isValidJSONObject:obj] ||\
(bool)[obj isKindOfClass:[NSString class]] ||\
(bool)[obj isKindOfClass:[NSNumber class]]))
(bool)[obj isKindOfClass:(Class)[NSString class]] ||\
(bool)[obj isKindOfClass:(Class)[NSNumber class]]))
#define RETURN(ret) ({\
if (!IS_JSON_OBJ(ret)) {\
(void)[NSException raise:@"Invalid RETURN argument" format:@""];\
}\
NSDictionary *__dict = @{@"return":ret};\
NSMutableDictionary *__dict = (id)[NSMutableDictionary dictionary];\
[__dict setValue:(id)ret forKey:@"return"];\
NSData *__data = (id)[NSJSONSerialization dataWithJSONObject:__dict options:0 error:NULL];\
NSString *__str = (id)[[NSString alloc] initWithData:__data encoding:4];\
(char *)[__str UTF8String];})
Expand All @@ -167,21 +168,23 @@ def check_expr(expr):
# Example:
# >>> fblldbbase.evaluate('NSString *str = @"hello world"; RETURN(@{@"key": str});')
# {u'key': u'hello world'}
def evaluate(expr):
def evaluate(expr, printErrors=True):
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, printErrors=True)
ret = evaluateExpressionValue(command, printErrors=printErrors)
if not ret.GetError().Success():
print ret.GetError()
if printErrors:
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
if printErrors:
print error
return None
else:
ret = json.loads(ret)
Expand Down