diff --git a/commands/FBClassDump.py b/commands/FBClassDump.py index 3615dd0..f81e4ee 100644 --- a/commands/FBClassDump.py +++ b/commands/FBClassDump.py @@ -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"]; if (real.flags & BLOCK_HAS_SIGNATURE) { char *signature; @@ -131,7 +131,7 @@ def run(self, arguments, options): [types addObject:(id)[NSString stringWithUTF8String:type]]; } - [dict setObject:types forKey:@"signature"]; + [dict setValue:types forKey:@"signature"]; } RETURN(dict); @@ -139,17 +139,116 @@ def run(self, arguments, options): 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): @@ -302,10 +401,10 @@ 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; @@ -313,9 +412,9 @@ def getProperties(klass): 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]; } diff --git a/fblldbbase.py b/fblldbbase.py index 3a8f5b1..6811b4e 100755 --- a/fblldbbase.py +++ b/fblldbbase.py @@ -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];}) @@ -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)