├── LICENSE ├── README.md └── libexec ├── commands ├── FBAccessibilityCommands.py ├── FBAutoLayoutCommands.py ├── FBClassDump.py ├── FBComponentCommands.py ├── FBDebugCommands.py ├── FBDelay.py ├── FBDisplayCommands.py ├── FBFindCommands.py ├── FBFlickerCommands.py ├── FBInvocationCommands.py ├── FBPrintCommands.py ├── FBTextInputCommands.py ├── FBVisualizationCommands.py └── FBXCTestCommands.py ├── fblldb.py ├── fblldbbase.py ├── fblldbinputhelpers.py ├── fblldbobjcruntimehelpers.py ├── fblldbobjecthelpers.py ├── fblldbviewcontrollerhelpers.py └── fblldbviewhelpers.py /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 jc 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Chisel-1.8.1 Support Python 3 2 | 3 | Replace libexec in the chisel directory, then restart Xcode. 4 | -------------------------------------------------------------------------------- /libexec/commands/FBAccessibilityCommands.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Copyright (c) 2015, Facebook, Inc. 4 | # All rights reserved. 5 | # 6 | # This source code is licensed under the BSD-style license found in the 7 | # LICENSE file in the root directory of this source tree. An additional grant 8 | # of patent rights can be found in the PATENTS file in the same directory. 9 | 10 | import re 11 | import os 12 | 13 | import lldb 14 | import fblldbbase as fb 15 | import fblldbobjecthelpers as objHelpers 16 | 17 | # This is the key corresponding to accessibility label in _accessibilityElementsInContainer: 18 | ACCESSIBILITY_LABEL_KEY = 2001 19 | 20 | def lldbcommands(): 21 | return [ 22 | FBPrintAccessibilityLabels(), 23 | FBPrintAccessibilityIdentifiers(), 24 | FBFindViewByAccessibilityLabelCommand(), 25 | ] 26 | 27 | class FBPrintAccessibilityLabels(fb.FBCommand): 28 | def name(self): 29 | return 'pa11y' 30 | 31 | def description(self): 32 | return 'Print accessibility labels of all views in hierarchy of ' 33 | 34 | def args(self): 35 | return [ fb.FBCommandArgument(arg='aView', type='UIView*', help='The view to print the hierarchy of.', default='(id)[[UIApplication sharedApplication] keyWindow]') ] 36 | 37 | def run(self, arguments, options): 38 | forceStartAccessibilityServer() 39 | printAccessibilityHierarchy(arguments[0]) 40 | 41 | class FBPrintAccessibilityIdentifiers(fb.FBCommand): 42 | def name(self): 43 | return 'pa11yi' 44 | 45 | def description(self): 46 | return 'Print accessibility identifiers of all views in hierarchy of ' 47 | 48 | def args(self): 49 | return [ fb.FBCommandArgument(arg='aView', type='UIView*', help='The view to print the hierarchy of.', default='(id)[[UIApplication sharedApplication] keyWindow]') ] 50 | 51 | def run(self, arguments, option): 52 | forceStartAccessibilityServer() 53 | printAccessibilityIdentifiersHierarchy(arguments[0]) 54 | 55 | class FBFindViewByAccessibilityLabelCommand(fb.FBCommand): 56 | def name(self): 57 | return 'fa11y' 58 | 59 | def description(self): 60 | return 'Find the views whose accessibility labels match labelRegex and puts the address of the first result on the clipboard.' 61 | 62 | def args(self): 63 | return [ fb.FBCommandArgument(arg='labelRegex', type='string', help='The accessibility label regex to search the view hierarchy for.') ] 64 | 65 | def accessibilityGrepHierarchy(self, view, needle): 66 | a11yLabel = accessibilityLabel(view) 67 | #if we don't have any accessibility string - we should have some children 68 | if int(a11yLabel.GetValue(), 16) == 0: 69 | #We call private method that gives back all visible accessibility children for view 70 | # iOS 10 and higher 71 | if fb.evaluateBooleanExpression('[UIView respondsToSelector:@selector(_accessibilityElementsAndContainersDescendingFromViews:options:sorted:)]'): 72 | accessibilityElements = fb.evaluateObjectExpression('[UIView _accessibilityElementsAndContainersDescendingFromViews:@[(id)%s] options:0 sorted:NO]' % view) 73 | else: 74 | accessibilityElements = fb.evaluateObjectExpression('[[[UIApplication sharedApplication] keyWindow] _accessibilityElementsInContainer:0 topLevel:%s includeKB:0]' % view) 75 | accessibilityElementsCount = fb.evaluateIntegerExpression('[%s count]' % accessibilityElements) 76 | for index in range(0, accessibilityElementsCount): 77 | subview = fb.evaluateObjectExpression('[%s objectAtIndex:%i]' % (accessibilityElements, index)) 78 | self.accessibilityGrepHierarchy(subview, needle) 79 | elif re.match(r'.*' + needle + '.*', a11yLabel.GetObjectDescription(), re.IGNORECASE): 80 | classDesc = objHelpers.className(view) 81 | print('({} {}) {}'.format(classDesc, view, a11yLabel.GetObjectDescription())) 82 | 83 | #First element that is found is copied to clipboard 84 | if not self.foundElement: 85 | self.foundElement = True 86 | cmd = 'echo %s | tr -d "\n" | pbcopy' % view 87 | os.system(cmd) 88 | 89 | def run(self, arguments, options): 90 | forceStartAccessibilityServer() 91 | rootView = fb.evaluateObjectExpression('[[UIApplication sharedApplication] keyWindow]') 92 | self.foundElement = False 93 | self.accessibilityGrepHierarchy(rootView, arguments[0]) 94 | 95 | def isRunningInSimulator(): 96 | return ((fb.evaluateExpressionValue('(id)[[UIDevice currentDevice] model]').GetObjectDescription().lower().find('simulator') >= 0) or (fb.evaluateExpressionValue('(id)[[UIDevice currentDevice] name]').GetObjectDescription().lower().find('simulator') >= 0)) 97 | 98 | def forceStartAccessibilityServer(): 99 | #We try to start accessibility server only if we don't have needed method active 100 | if not fb.evaluateBooleanExpression('[UIView instancesRespondToSelector:@selector(_accessibilityElementsInContainer:)]'): 101 | #Starting accessibility server is different for simulator and device 102 | if isRunningInSimulator(): 103 | fb.evaluateEffect('[[UIApplication sharedApplication] accessibilityActivate]') 104 | else: 105 | fb.evaluateEffect('[[[UIApplication sharedApplication] _accessibilityBundlePrincipalClass] _accessibilityStartServer]') 106 | 107 | def accessibilityLabel(view): 108 | #using Apple private API to get real value of accessibility string for element. 109 | return fb.evaluateExpressionValue('(id)[%s accessibilityAttributeValue:%i]' % (view, ACCESSIBILITY_LABEL_KEY), False) 110 | 111 | def accessibilityIdentifier(view): 112 | return fb.evaluateExpressionValue('(id)[{} accessibilityIdentifier]'.format(view), False) 113 | 114 | def accessibilityElements(view): 115 | if fb.evaluateBooleanExpression('[UIView instancesRespondToSelector:@selector(accessibilityElements)]'): 116 | a11yElements = fb.evaluateExpression('(id)[%s accessibilityElements]' % view, False) 117 | if int(a11yElements, 16) != 0: 118 | return a11yElements 119 | if fb.evaluateBooleanExpression('[%s respondsToSelector:@selector(_accessibleSubviews)]' % view): 120 | return fb.evaluateExpression('(id)[%s _accessibleSubviews]' % (view), False) 121 | else: 122 | return fb.evaluateObjectExpression('[[[UIApplication sharedApplication] keyWindow] _accessibilityElementsInContainer:0 topLevel:%s includeKB:0]' % view) 123 | 124 | def printAccessibilityHierarchy(view, indent = 0): 125 | a11yLabel = accessibilityLabel(view) 126 | classDesc = objHelpers.className(view) 127 | indentString = ' | ' * indent 128 | 129 | #if we don't have any accessibility string - we should have some children 130 | if int(a11yLabel.GetValue(), 16) == 0: 131 | print (indentString + ('{} {}'.format(classDesc, view))) 132 | #We call private method that gives back all visible accessibility children for view 133 | a11yElements = accessibilityElements(view) 134 | accessibilityElementsCount = int(fb.evaluateExpression('(int)[%s count]' % a11yElements)) 135 | for index in range(0, accessibilityElementsCount): 136 | subview = fb.evaluateObjectExpression('[%s objectAtIndex:%i]' % (a11yElements, index)) 137 | printAccessibilityHierarchy(subview, indent + 1) 138 | else: 139 | print (indentString + ('({} {}) {}'.format(classDesc, view, a11yLabel.GetObjectDescription()))) 140 | 141 | def printAccessibilityIdentifiersHierarchy(view, indent = 0): 142 | a11yIdentifier = accessibilityIdentifier(view) 143 | classDesc = objHelpers.className(view) 144 | indentString = ' | ' * indent 145 | 146 | #if we don't have any accessibility identifier - we should have some children 147 | if int(a11yIdentifier.GetValue(), 16) == 0: 148 | print (indentString + ('{} {}'.format(classDesc, view))) 149 | #We call private method that gives back all visible accessibility children for view 150 | a11yElements = accessibilityElements(view) 151 | accessibilityElementsCount = int(fb.evaluateExpression('(int)[%s count]' % a11yElements)) 152 | for index in range(0, accessibilityElementsCount): 153 | subview = fb.evaluateObjectExpression('[%s objectAtIndex:%i]' % (a11yElements, index)) 154 | printAccessibilityIdentifiersHierarchy(subview, indent + 1) 155 | else: 156 | print (indentString + ('({} {}) {}'.format(classDesc, view, a11yIdentifier.GetObjectDescription()))) 157 | -------------------------------------------------------------------------------- /libexec/commands/FBAutoLayoutCommands.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Copyright (c) 2014, Facebook, Inc. 4 | # All rights reserved. 5 | # 6 | # This source code is licensed under the BSD-style license found in the 7 | # LICENSE file in the root directory of this source tree. An additional grant 8 | # of patent rights can be found in the PATENTS file in the same directory. 9 | 10 | 11 | import lldb 12 | import fblldbbase as fb 13 | import fblldbviewhelpers as viewHelpers 14 | 15 | def lldbcommands(): 16 | return [ 17 | FBPrintAutolayoutTrace(), 18 | FBAutolayoutBorderAmbiguous(), 19 | FBAutolayoutUnborderAmbiguous(), 20 | ] 21 | 22 | class FBPrintAutolayoutTrace(fb.FBCommand): 23 | def name(self): 24 | return 'paltrace' 25 | 26 | def description(self): 27 | return "Print the Auto Layout trace for the given view. Defaults to the key window." 28 | 29 | def args(self): 30 | return [ fb.FBCommandArgument(arg='view', type='UIView *', help='The view to print the Auto Layout trace for.', default='(id)[[UIApplication sharedApplication] keyWindow]') ] 31 | 32 | def run(self, arguments, options): 33 | view = fb.evaluateInputExpression(arguments[0]) 34 | opt = fb.evaluateBooleanExpression('[UIView instancesRespondToSelector:@selector(_autolayoutTraceRecursively:)]') 35 | traceCall = '_autolayoutTraceRecursively:1' if opt else '_autolayoutTrace' 36 | print (fb.describeObject('[{} {}]'.format(view, traceCall))) 37 | 38 | 39 | def setBorderOnAmbiguousViewRecursive(view, width, color): 40 | if not fb.evaluateBooleanExpression('[(id)%s isKindOfClass:(Class)[UIView class]]' % view): 41 | return 42 | 43 | isAmbiguous = fb.evaluateBooleanExpression('(BOOL)[%s hasAmbiguousLayout]' % view) 44 | if isAmbiguous: 45 | layer = viewHelpers.convertToLayer(view) 46 | fb.evaluateEffect('[%s setBorderWidth:(CGFloat)%s]' % (layer, width)) 47 | fb.evaluateEffect('[%s setBorderColor:(CGColorRef)[(id)[UIColor %sColor] CGColor]]' % (layer, color)) 48 | 49 | subviews = fb.evaluateExpression('(id)[%s subviews]' % view) 50 | subviewsCount = int(fb.evaluateExpression('(int)[(id)%s count]' % subviews)) 51 | if subviewsCount > 0: 52 | for i in range(0, subviewsCount): 53 | subview = fb.evaluateExpression('(id)[%s objectAtIndex:%i]' % (subviews, i)) 54 | setBorderOnAmbiguousViewRecursive(subview, width, color) 55 | 56 | 57 | class FBAutolayoutBorderAmbiguous(fb.FBCommand): 58 | def name(self): 59 | return 'alamborder' 60 | 61 | def description(self): 62 | return "Put a border around views with an ambiguous layout" 63 | 64 | def options(self): 65 | return [ 66 | fb.FBCommandArgument(short='-c', long='--color', arg='color', type='string', default='red', help='A color name such as \'red\', \'green\', \'magenta\', etc.'), 67 | fb.FBCommandArgument(short='-w', long='--width', arg='width', type='CGFloat', default=2.0, help='Desired width of border.') 68 | ] 69 | 70 | def run(self, arguments, options): 71 | keyWindow = fb.evaluateExpression('(id)[[UIApplication sharedApplication] keyWindow]') 72 | setBorderOnAmbiguousViewRecursive(keyWindow, options.width, options.color) 73 | lldb.debugger.HandleCommand('caflush') 74 | 75 | 76 | class FBAutolayoutUnborderAmbiguous(fb.FBCommand): 77 | def name(self): 78 | return 'alamunborder' 79 | 80 | def description(self): 81 | return "Removes the border around views with an ambiguous layout" 82 | 83 | def run(self, arguments, options): 84 | keyWindow = fb.evaluateExpression('(id)[[UIApplication sharedApplication] keyWindow]') 85 | setBorderOnAmbiguousViewRecursive(keyWindow, 0, "red") 86 | lldb.debugger.HandleCommand('caflush') 87 | -------------------------------------------------------------------------------- /libexec/commands/FBClassDump.py: -------------------------------------------------------------------------------- 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 | FBPrintProperties(), 11 | FBPrintBlock() 12 | ] 13 | 14 | class FBPrintMethods(fb.FBCommand): 15 | def name(self): 16 | return 'pmethods' 17 | 18 | def description(self): 19 | return 'Print the class and instance methods of a class.' 20 | 21 | def options(self): 22 | return [ 23 | fb.FBCommandArgument(short='-a', long='--address', arg='showaddr', help='Print the implementation address of the method', default=False, boolean=True), 24 | fb.FBCommandArgument(short='-i', long='--instance', arg='insmethod', help='Print the instance methods', default=False, boolean=True), 25 | fb.FBCommandArgument(short='-c', long='--class', arg='clsmethod', help='Print the class methods', default=False, boolean=True), 26 | fb.FBCommandArgument(short='-n', long='--name', arg='clsname', help='Take the argument as class name', default=False, boolean=True) 27 | ] 28 | 29 | def args(self): 30 | return [ fb.FBCommandArgument(arg='instance or class', type='instance or Class', help='an Objective-C Class.') ] 31 | 32 | def run(self, arguments, options): 33 | cls = getClassFromArgument(arguments[0], options.clsname) 34 | 35 | if options.clsmethod: 36 | print ('Class Methods:') 37 | printClassMethods(cls, options.showaddr) 38 | 39 | if options.insmethod: 40 | print ('\nInstance Methods:') 41 | printInstanceMethods(cls, options.showaddr) 42 | 43 | if not options.clsmethod and not options.insmethod: 44 | print ('Class Methods:') 45 | printClassMethods(cls, options.showaddr) 46 | print ('\nInstance Methods:') 47 | printInstanceMethods(cls, options.showaddr) 48 | 49 | 50 | class FBPrintProperties(fb.FBCommand): 51 | 52 | def name(self): 53 | return 'pproperties' 54 | 55 | def description(self): 56 | return "Print the properties of an instance or Class" 57 | 58 | def options(self): 59 | return [ 60 | fb.FBCommandArgument(short='-n', long='--name', arg='clsname', help='Take the argument as class name', default=False, boolean=True) 61 | ] 62 | 63 | def args(self): 64 | return [ fb.FBCommandArgument(arg='instance or class', type='instance or Class', help='an Objective-C Class.') ] 65 | 66 | def run(self, arguments, options): 67 | cls = getClassFromArgument(arguments[0], options.clsname) 68 | 69 | printProperties(cls) 70 | 71 | class FBPrintBlock(fb.FBCommand): 72 | def name(self): 73 | return 'pblock' 74 | 75 | def description(self): 76 | return 'Print the block`s implementation address and signature' 77 | 78 | def args(self): 79 | return [ 80 | fb.FBCommandArgument(arg='block', help='The block object you want to print'), 81 | ] 82 | 83 | def run(self, arguments, options): 84 | block = arguments[0] 85 | 86 | # http://clang.llvm.org/docs/Block-ABI-Apple.html 87 | tmpString = """ 88 | enum { 89 | BLOCK_HAS_COPY_DISPOSE = (1 << 25), 90 | BLOCK_HAS_CTOR = (1 << 26), // helpers have C++ code 91 | BLOCK_IS_GLOBAL = (1 << 28), 92 | BLOCK_HAS_STRET = (1 << 29), // IFF BLOCK_HAS_SIGNATURE 93 | BLOCK_HAS_SIGNATURE = (1 << 30), 94 | }; 95 | struct Block_literal_1 { 96 | void *isa; // initialized to &_NSConcreteStackBlock or &_NSConcreteGlobalBlock 97 | int flags; 98 | int reserved; 99 | void (*invoke)(void *, ...); 100 | struct Block_descriptor_1 { 101 | unsigned long int reserved; // NULL 102 | unsigned long int size; // sizeof(struct Block_literal_1) 103 | // optional helper functions 104 | void (*copy_helper)(void *dst, void *src); // IFF (1<<25) 105 | void (*dispose_helper)(void *src); // IFF (1<<25) 106 | // required ABI.2010.3.16 107 | const char *signature; // IFF (1<<30) 108 | } *descriptor; 109 | // imported variables 110 | }; 111 | struct Block_literal_1 real = *((__bridge struct Block_literal_1 *)$block); 112 | NSMutableDictionary *dict = (id)[NSMutableDictionary dictionary]; 113 | 114 | [dict setObject:(id)[NSNumber numberWithLong:(long)real.invoke] forKey:@"invoke"]; 115 | 116 | if (real.flags & BLOCK_HAS_SIGNATURE) { 117 | char *signature; 118 | if (real.flags & BLOCK_HAS_COPY_DISPOSE) { 119 | signature = (char *)(real.descriptor)->signature; 120 | } else { 121 | signature = (char *)(real.descriptor)->copy_helper; 122 | } 123 | 124 | NSMethodSignature *sig = [NSMethodSignature signatureWithObjCTypes:signature]; 125 | NSMutableArray *types = [NSMutableArray array]; 126 | 127 | [types addObject:(id)[NSString stringWithUTF8String:(char *)[sig methodReturnType]]]; 128 | 129 | for (NSUInteger i = 0; i < sig.numberOfArguments; i++) { 130 | char *type = (char *)[sig getArgumentTypeAtIndex:i]; 131 | [types addObject:(id)[NSString stringWithUTF8String:type]]; 132 | } 133 | 134 | [dict setObject:types forKey:@"signature"]; 135 | } 136 | 137 | RETURN(dict); 138 | """ 139 | command = string.Template(tmpString).substitute(block=block) 140 | json = fb.evaluate(command) 141 | 142 | signature = json['signature'] 143 | if not signature: 144 | print ('Imp: ' + hex(json['invoke'])) 145 | return 146 | 147 | sigStr = '{} ^('.format(decode(signature[0])) 148 | # the block`s implementation always take the block as it`s first argument, so we ignore it 149 | sigStr += ', '.join([decode(m) for m in signature[2:]]) 150 | sigStr += ');' 151 | 152 | print ('Imp: ' + hex(json['invoke']) + ' Signature: ' + sigStr) 153 | 154 | # helpers 155 | def isClassObject(arg): 156 | return runtimeHelpers.class_isMetaClass(runtimeHelpers.object_getClass(arg)) 157 | 158 | def getClassFromArgument(arg, is_classname): 159 | cls = arg 160 | if is_classname: 161 | cls = runtimeHelpers.objc_getClass(cls) 162 | if not int(cls, 16): 163 | raise Exception('Class "{}" not found'.format(arg)) 164 | else: 165 | if not isClassObject(cls): 166 | cls = runtimeHelpers.object_getClass(cls) 167 | if not isClassObject(cls): 168 | raise Exception('Invalid argument. Please specify an instance or a Class.') 169 | 170 | return cls 171 | 172 | def printInstanceMethods(cls, showaddr=False, prefix='-'): 173 | methods = getMethods(cls) 174 | if not methods: 175 | print ("No methods were found") 176 | 177 | for m in methods: 178 | if showaddr: 179 | print (prefix + ' ' + m.prettyPrintString() + ' ' + str(m.imp)) 180 | else: 181 | print (prefix + ' ' + m.prettyPrintString()) 182 | 183 | def printClassMethods(cls, showaddr=False): 184 | printInstanceMethods(runtimeHelpers.object_getClass(cls), showaddr, '+') 185 | 186 | def printProperties(cls, showvalue=False): 187 | props = getProperties(cls) 188 | for p in props: 189 | print (p.prettyPrintString()) 190 | 191 | def decode(code): 192 | encodeMap = { 193 | 'c': 'char', 194 | 'i': 'int', 195 | 's': 'short', 196 | 'l': 'long', 197 | 'q': 'long long', 198 | 199 | 'C': 'unsigned char', 200 | 'I': 'unsigned int', 201 | 'S': 'unsigned short', 202 | 'L': 'unsigned long', 203 | 'Q': 'unsigned long long', 204 | 205 | 'f': 'float', 206 | 'd': 'double', 207 | 'B': 'bool', 208 | 'v': 'void', 209 | '*': 'char *', 210 | '@': 'id', 211 | '#': 'Class', 212 | ':': 'SEL', 213 | } 214 | 215 | ret = code 216 | if code in encodeMap: 217 | ret = encodeMap[code] 218 | elif ret[0:1] == '@': 219 | if ret[1:2] == '?': # @? represent a block 220 | ret = code 221 | elif ret[2:3] == '<': # @"" 222 | ret = 'id' + ret[2:-1].replace('><', ', ') 223 | else: 224 | ret = ret[2:-1] + ' *' 225 | elif ret[0:1] == '^': 226 | ret = decode(ret[1:]) + ' *' 227 | 228 | return ret 229 | 230 | # Notice that evaluateExpression doesn't work with variable arguments. such as -[NSString stringWithFormat:] 231 | # I remove the "free(methods)" because it would cause evaluateExpressionValue to raise exception some time. 232 | def getMethods(klass): 233 | tmpString = """ 234 | unsigned int outCount; 235 | Method *methods = (Method *)class_copyMethodList((Class)$cls, &outCount); 236 | NSMutableArray *result = (id)[NSMutableArray array]; 237 | 238 | for (int i = 0; i < outCount; i++) { 239 | NSMutableDictionary *m = (id)[NSMutableDictionary dictionary]; 240 | 241 | SEL name = (SEL)method_getName(methods[i]); 242 | [m setObject:(id)NSStringFromSelector(name) forKey:@"name"]; 243 | 244 | char * encoding = (char *)method_getTypeEncoding(methods[i]); 245 | [m setObject:(id)[NSString stringWithUTF8String:encoding] forKey:@"type_encoding"]; 246 | 247 | NSMutableArray *types = (id)[NSMutableArray array]; 248 | NSInteger args = (NSInteger)method_getNumberOfArguments(methods[i]); 249 | for (int idx = 0; idx < args; idx++) { 250 | char *type = (char *)method_copyArgumentType(methods[i], idx); 251 | [types addObject:(id)[NSString stringWithUTF8String:type]]; 252 | } 253 | [m setObject:types forKey:@"parameters_type"]; 254 | 255 | char *ret_type = (char *)method_copyReturnType(methods[i]); 256 | [m setObject:(id)[NSString stringWithUTF8String:ret_type] forKey:@"return_type"]; 257 | 258 | long imp = (long)method_getImplementation(methods[i]); 259 | [m setObject:[NSNumber numberWithLongLong:imp] forKey:@"implementation"]; 260 | 261 | [result addObject:m]; 262 | } 263 | RETURN(result); 264 | """ 265 | command = string.Template(tmpString).substitute(cls=klass) 266 | methods = fb.evaluate(command) 267 | return [Method(m) for m in methods] 268 | 269 | class Method: 270 | 271 | def __init__(self, json): 272 | self.name = json['name'] 273 | self.type_encoding = json['type_encoding'] 274 | self.parameters_type = json['parameters_type'] 275 | self.return_type = json['return_type'] 276 | self.imp = self.toHex(json['implementation']) 277 | 278 | def prettyPrintString(self): 279 | argnum = len(self.parameters_type) 280 | names = self.name.split(':') 281 | 282 | # the argnum count must be bigger then 2, index 0 for self, index 1 for SEL 283 | for i in range(2, argnum): 284 | arg_type = self.parameters_type[i] 285 | names[i-2] = names[i-2] + ":(" + decode(arg_type) + ")arg" + str(i-2) 286 | 287 | string = " ".join(names) 288 | return "({}){}".format(decode(self.return_type), string) 289 | 290 | def toHex(self, addr): 291 | return hex(addr) 292 | 293 | def __str__(self): 294 | return " " + self.name + " --- " + self.type + " --- " + self.imp 295 | 296 | def getProperties(klass): 297 | tmpString = """ 298 | NSMutableArray *result = (id)[NSMutableArray array]; 299 | unsigned int count; 300 | objc_property_t *props = (objc_property_t *)class_copyPropertyList((Class)$cls, &count); 301 | for (int i = 0; i < count; i++) { 302 | NSMutableDictionary *dict = (id)[NSMutableDictionary dictionary]; 303 | 304 | char *name = (char *)property_getName(props[i]); 305 | [dict setObject:(id)[NSString stringWithUTF8String:name] forKey:@"name"]; 306 | 307 | char *attrstr = (char *)property_getAttributes(props[i]); 308 | [dict setObject:(id)[NSString stringWithUTF8String:attrstr] forKey:@"attributes_string"]; 309 | 310 | NSMutableDictionary *attrsDict = (id)[NSMutableDictionary dictionary]; 311 | unsigned int pcount; 312 | objc_property_attribute_t *attrs = (objc_property_attribute_t *)property_copyAttributeList(props[i], &pcount); 313 | for (int i = 0; i < pcount; i++) { 314 | NSString *name = (id)[NSString stringWithUTF8String:(char *)attrs[i].name]; 315 | NSString *value = (id)[NSString stringWithUTF8String:(char *)attrs[i].value]; 316 | [attrsDict setObject:value forKey:name]; 317 | } 318 | [dict setObject:attrsDict forKey:@"attributes"]; 319 | 320 | [result addObject:dict]; 321 | } 322 | RETURN(result); 323 | """ 324 | command = string.Template(tmpString).substitute(cls=klass) 325 | propsJson = fb.evaluate(command) 326 | return [Property(m) for m in propsJson] 327 | 328 | class Property: 329 | 330 | def __init__(self, json): 331 | self.name = json['name'] 332 | self.attributes_string = json['attributes_string'] 333 | self.attributes = json['attributes'] 334 | 335 | # https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtPropertyIntrospection.html#//apple_ref/doc/uid/TP40008048-CH101-SW1 336 | def prettyPrintString(self): 337 | attrs = [] 338 | if self.attributes.has_key('N'): 339 | attrs.append('nonatomic') 340 | else: 341 | attrs.append('atomic') 342 | 343 | if self.attributes.has_key('&'): 344 | attrs.append('strong') 345 | elif self.attributes.has_key('C'): 346 | attrs.append('copy') 347 | elif self.attributes.has_key('W'): 348 | attrs.append('weak') 349 | else: 350 | attrs.append('assign') 351 | 352 | if self.attributes.has_key('R'): 353 | attrs.append('readonly') 354 | 355 | if self.attributes.has_key('G'): 356 | attrs.append("getter={}".format(self.attributes['G'])) 357 | if self.attributes.has_key('S'): 358 | attrs.append("setter={}".format(self.attributes['S'])) 359 | 360 | return "@property ({}) {} {};".format(", ".join(attrs), decode(self.attributes['T']), self.name) 361 | -------------------------------------------------------------------------------- /libexec/commands/FBComponentCommands.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import os 4 | 5 | import lldb 6 | import fblldbbase as fb 7 | import fblldbviewhelpers as viewHelpers 8 | 9 | def lldbcommands(): 10 | return [ 11 | FBComponentsDebugCommand(), 12 | FBComponentsPrintCommand(), 13 | FBComponentsReflowCommand(), 14 | ] 15 | 16 | 17 | class FBComponentsDebugCommand(fb.FBCommand): 18 | def name(self): 19 | return 'dcomponents' 20 | 21 | def description(self): 22 | return 'Set debugging options for components.' 23 | 24 | def options(self): 25 | return [ 26 | fb.FBCommandArgument(short='-s', long='--set', arg='set', help='Set debug mode for components', boolean=True), 27 | fb.FBCommandArgument(short='-u', long='--unset', arg='unset', help='Unset debug mode for components', boolean=True), 28 | ] 29 | 30 | def run(self, arguments, options): 31 | if options.set: 32 | fb.evaluateEffect('[CKComponentDebugController setDebugMode:YES]') 33 | print ('Debug mode for ComponentKit has been set.') 34 | elif options.unset: 35 | fb.evaluateEffect('[CKComponentDebugController setDebugMode:NO]') 36 | print ('Debug mode for ComponentKit has been unset.') 37 | else: 38 | print ('No option for ComponentKit Debug mode specified.') 39 | 40 | class FBComponentsPrintCommand(fb.FBCommand): 41 | def name(self): 42 | return 'pcomponents' 43 | 44 | def description(self): 45 | return 'Print a recursive description of components found starting from .' 46 | 47 | def options(self): 48 | return [ 49 | fb.FBCommandArgument(short='-u', long='--up', arg='upwards', boolean=True, default=False, help='Print only the component hierarchy found on the first superview that has them, carrying the search up to its window.'), 50 | fb.FBCommandArgument(short='-v', long='--show-views', arg='showViews', type='BOOL', default='YES', help='Prints the component hierarchy and does not print the views if the supplied argument is \'NO\'. Supply either a \'YES\' or a \'NO\'. The default is to show views.'), 51 | ] 52 | 53 | def args(self): 54 | return [ fb.FBCommandArgument(arg='aView', type='UIView* or CKComponent*', help='The view or component from which the search for components begins.', default='(id)[[UIApplication sharedApplication] keyWindow]') ] 55 | 56 | def run(self, arguments, options): 57 | upwards = 'YES' if options.upwards else 'NO' 58 | showViews = 'YES' if options.showViews == 'YES' else 'NO' 59 | 60 | view = fb.evaluateInputExpression(arguments[0]) 61 | if not viewHelpers.isView(view): 62 | # assume it's a CKComponent 63 | view = fb.evaluateExpression('((CKComponent *)%s).viewContext.view' % view) 64 | 65 | print (fb.describeObject('[CKComponentHierarchyDebugHelper componentHierarchyDescriptionForView:(UIView *)' + view + ' searchUpwards:' + upwards + ' showViews:' + showViews + ']')) 66 | 67 | class FBComponentsReflowCommand(fb.FBCommand): 68 | def name(self): 69 | return 'rcomponents' 70 | 71 | def description(self): 72 | return 'Synchronously reflow and update all components.' 73 | 74 | def run(self, arguments, options): 75 | fb.evaluateEffect('[CKComponentDebugController reflowComponents]') 76 | -------------------------------------------------------------------------------- /libexec/commands/FBDebugCommands.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import lldb 4 | import fblldbbase as fb 5 | import fblldbobjcruntimehelpers as objc 6 | 7 | import sys 8 | import os 9 | import re 10 | 11 | def lldbcommands(): 12 | return [ 13 | FBWatchInstanceVariableCommand(), 14 | FBFrameworkAddressBreakpointCommand(), 15 | FBMethodBreakpointCommand(), 16 | FBMemoryWarningCommand(), 17 | FBFindInstancesCommand(), 18 | FBMethodBreakpointEnableCommand(), 19 | FBMethodBreakpointDisableCommand(), 20 | FBHeapFromCommand(), 21 | FBSequenceCommand(), 22 | ] 23 | 24 | class FBWatchInstanceVariableCommand(fb.FBCommand): 25 | def name(self): 26 | return 'wivar' 27 | 28 | def description(self): 29 | return "Set a watchpoint for an object's instance variable." 30 | 31 | def args(self): 32 | return [ 33 | fb.FBCommandArgument(arg='object', type='id', help='Object expression to be evaluated.'), 34 | fb.FBCommandArgument(arg='ivarName', help='Name of the instance variable to watch.') 35 | ] 36 | 37 | def run(self, arguments, options): 38 | commandForObject, ivarName = arguments 39 | 40 | objectAddress = int(fb.evaluateObjectExpression(commandForObject), 0) 41 | 42 | ivarOffsetCommand = '(ptrdiff_t)ivar_getOffset((void*)object_getInstanceVariable((id){}, "{}", 0))'.format(objectAddress, ivarName) 43 | ivarOffset = int(fb.evaluateExpression(ivarOffsetCommand), 0) 44 | 45 | # A multi-statement command allows for variables scoped to the command, not permanent in the session like $variables. 46 | ivarSizeCommand = ('unsigned int size = 0;' 47 | 'char *typeEncoding = (char *)ivar_getTypeEncoding((void*)class_getInstanceVariable((Class)object_getClass((id){}), "{}"));' 48 | '(char *)NSGetSizeAndAlignment(typeEncoding, &size, 0);' 49 | 'size').format(objectAddress, ivarName) 50 | ivarSize = int(fb.evaluateExpression(ivarSizeCommand), 0) 51 | 52 | error = lldb.SBError() 53 | watchpoint = lldb.debugger.GetSelectedTarget().WatchAddress(objectAddress + ivarOffset, ivarSize, False, True, error) 54 | 55 | if error.Success(): 56 | print ('Remember to delete the watchpoint using: watchpoint delete {}'.format(watchpoint.GetID())) 57 | else: 58 | print ('Could not create the watchpoint: {}'.format(error.GetCString())) 59 | 60 | class FBFrameworkAddressBreakpointCommand(fb.FBCommand): 61 | def name(self): 62 | return 'binside' 63 | 64 | def description(self): 65 | return "Set a breakpoint for a relative address within the framework/library that's currently running. This does the work of finding the offset for the framework/library and sliding your address accordingly." 66 | 67 | def args(self): 68 | return [ 69 | fb.FBCommandArgument(arg='address', type='string', help='Address within the currently running framework to set a breakpoint on.'), 70 | ] 71 | 72 | def run(self, arguments, options): 73 | library_address = int(arguments[0], 0) 74 | address = int(lldb.debugger.GetSelectedTarget().GetProcess().GetSelectedThread().GetSelectedFrame().GetModule().ResolveFileAddress(library_address)) 75 | 76 | lldb.debugger.HandleCommand('breakpoint set --address {}'.format(address)) 77 | 78 | class FBMethodBreakpointCommand(fb.FBCommand): 79 | def name(self): 80 | return 'bmessage' 81 | 82 | def description(self): 83 | return "Set a breakpoint for a selector on a class, even if the class itself doesn't override that selector. It walks the hierarchy until it finds a class that does implement the selector and sets a conditional breakpoint there." 84 | 85 | def args(self): 86 | return [ 87 | fb.FBCommandArgument(arg='expression', type='string', help='Expression to set a breakpoint on, e.g. "-[MyView setFrame:]", "+[MyView awesomeClassMethod]" or "-[0xabcd1234 setFrame:]"'), 88 | ] 89 | 90 | def run(self, arguments, options): 91 | expression = arguments[0] 92 | 93 | methodPattern = re.compile(r""" 94 | (?P[-+])? 95 | \[ 96 | (?P.*?) 97 | (?P\(.+\))? 98 | \s+ 99 | (?P.*) 100 | \] 101 | """, re.VERBOSE) 102 | 103 | match = methodPattern.match(expression) 104 | 105 | if not match: 106 | print ('Failed to parse expression. Do you even Objective-C?!') 107 | return 108 | 109 | expressionForSelf = objc.functionPreambleExpressionForSelf() 110 | if not expressionForSelf: 111 | print ('Your architecture, {}, is truly fantastic. However, I don\'t currently support it.'.format(arch)) 112 | return 113 | 114 | methodTypeCharacter = match.group('scope') 115 | classNameOrExpression = match.group('target') 116 | category = match.group('category') 117 | selector = match.group('selector') 118 | 119 | methodIsClassMethod = (methodTypeCharacter == '+') 120 | 121 | if not methodIsClassMethod: 122 | # The default is instance method, and methodTypeCharacter may not actually be '-'. 123 | methodTypeCharacter = '-' 124 | 125 | targetIsClass = False 126 | targetObject = fb.evaluateObjectExpression('({})'.format(classNameOrExpression), False) 127 | 128 | if not targetObject: 129 | # If the expression didn't yield anything then it's likely a class. Assume it is. 130 | # We will check again that the class does actually exist anyway. 131 | targetIsClass = True 132 | targetObject = fb.evaluateObjectExpression('[{} class]'.format(classNameOrExpression), False) 133 | 134 | targetClass = fb.evaluateObjectExpression('[{} class]'.format(targetObject), False) 135 | 136 | if not targetClass or int(targetClass, 0) == 0: 137 | print ('Couldn\'t find a class from the expression "{}". Did you typo?'.format(classNameOrExpression)) 138 | return 139 | 140 | if methodIsClassMethod: 141 | targetClass = objc.object_getClass(targetClass) 142 | 143 | found = False 144 | nextClass = targetClass 145 | 146 | while not found and int(nextClass, 0) > 0: 147 | if classItselfImplementsSelector(nextClass, selector): 148 | found = True 149 | else: 150 | nextClass = objc.class_getSuperclass(nextClass) 151 | 152 | if not found: 153 | print ('There doesn\'t seem to be an implementation of {} in the class hierarchy. Made a boo boo with the selector name?'.format(selector)) 154 | return 155 | 156 | breakpointClassName = objc.class_getName(nextClass) 157 | formattedCategory = category if category else '' 158 | breakpointFullName = '{}[{}{} {}]'.format(methodTypeCharacter, breakpointClassName, formattedCategory, selector) 159 | 160 | if targetIsClass: 161 | breakpointCondition = '(void*)object_getClass({}) == {}'.format(expressionForSelf, targetClass) 162 | else: 163 | breakpointCondition = '(void*){} == {}'.format(expressionForSelf, targetObject) 164 | 165 | print ('Setting a breakpoint at {} with condition {}'.format(breakpointFullName, breakpointCondition)) 166 | 167 | if category: 168 | lldb.debugger.HandleCommand('breakpoint set --skip-prologue false --fullname "{}" --condition "{}"'.format(breakpointFullName, breakpointCondition)) 169 | else: 170 | breakpointPattern = '{}\[{}(\(.+\))? {}\]'.format(methodTypeCharacter, breakpointClassName, selector) 171 | lldb.debugger.HandleCommand('breakpoint set --skip-prologue false --func-regex "{}" --condition "{}"'.format(breakpointPattern, breakpointCondition)) 172 | 173 | def classItselfImplementsSelector(klass, selector): 174 | thisMethod = objc.class_getInstanceMethod(klass, selector) 175 | if thisMethod == 0: 176 | return False 177 | 178 | superklass = objc.class_getSuperclass(klass) 179 | superMethod = objc.class_getInstanceMethod(superklass, selector) 180 | if thisMethod == superMethod: 181 | return False 182 | else: 183 | return True 184 | 185 | class FBMemoryWarningCommand(fb.FBCommand): 186 | def name(self): 187 | return 'mwarning' 188 | 189 | def description(self): 190 | return 'simulate a memory warning' 191 | 192 | def run(self, arguments, options): 193 | fb.evaluateEffect('[[UIApplication sharedApplication] performSelector:@selector(_performMemoryWarning)]') 194 | 195 | 196 | def switchBreakpointState(expression,on): 197 | 198 | expression_pattern = re.compile(r'{}'.format(expression),re.I) 199 | 200 | target = lldb.debugger.GetSelectedTarget() 201 | for breakpoint in target.breakpoint_iter(): 202 | if breakpoint.IsEnabled() != on and (expression_pattern.search(str(breakpoint))): 203 | print (str(breakpoint)) 204 | breakpoint.SetEnabled(on) 205 | for location in breakpoint: 206 | if location.IsEnabled() != on and (expression_pattern.search(str(location)) or expression == hex(location.GetAddress()) ): 207 | print (str(location)) 208 | location.SetEnabled(on) 209 | 210 | class FBMethodBreakpointEnableCommand(fb.FBCommand): 211 | def name(self): 212 | return 'benable' 213 | 214 | def description(self): 215 | return """ 216 | Enable a set of breakpoints for a regular expression 217 | 218 | Examples: 219 | 220 | * benable ***address*** 221 | benable 0x0000000104514dfc 222 | benable 0x183e23564 223 | 224 | #use `benable *filename*` to switch all breakpoints in this file to `enable` 225 | benable SUNNetService.m 226 | 227 | #use `benable ***module(AppName)***` to switch all breakpoints in this module to `enable` 228 | benable UIKit 229 | benable Foundation 230 | 231 | """ 232 | 233 | def args(self): 234 | return [ 235 | fb.FBCommandArgument(arg='expression', type='string', help='Expression to enable breakpoint'), 236 | ] 237 | 238 | def run(self, arguments, options): 239 | expression = arguments[0] 240 | switchBreakpointState(expression,True) 241 | 242 | class FBMethodBreakpointDisableCommand(fb.FBCommand): 243 | def name(self): 244 | return 'bdisable' 245 | 246 | def description(self): 247 | return """ 248 | Disable a set of breakpoints for a regular expression 249 | 250 | Examples: 251 | 252 | * bdisable ***address*** 253 | bdisable 0x0000000104514dfc 254 | bdisable 0x183e23564 255 | 256 | #use `bdisable *filename*` to switch all breakpoints in this file to `disable` 257 | bdisable SUNNetService.m 258 | 259 | #use `bdisable ***module(AppName)***` to switch all breakpoints in this module to `disable` 260 | bdisable UIKit 261 | bdisable Foundation 262 | 263 | """ 264 | def args(self): 265 | return [ 266 | fb.FBCommandArgument(arg='expression', type='string', help='Expression to disable breakpoint'), 267 | ] 268 | 269 | def run(self, arguments, options): 270 | expression = arguments[0] 271 | switchBreakpointState(expression,False) 272 | 273 | class FBFindInstancesCommand(fb.FBCommand): 274 | def name(self): 275 | return 'findinstances' 276 | 277 | def args(self): 278 | return [ 279 | fb.FBCommandArgument(arg='type', help='Class or protocol name'), 280 | fb.FBCommandArgument(arg='query', default=' ', # space is a hack to mark optional 281 | help='Query expression, uses NSPredicate syntax') 282 | ] 283 | 284 | def description(self): 285 | return """ 286 | Find instances of specified ObjC classes. 287 | 288 | This command scans memory and uses heuristics to identify instances of 289 | Objective-C classes. This includes Swift classes that descend from NSObject. 290 | 291 | Basic examples: 292 | 293 | findinstances UIScrollView 294 | findinstances *UIScrollView 295 | findinstances UIScrollViewDelegate 296 | 297 | These basic searches find instances of the given class or protocol. By 298 | default, subclasses of the class or protocol are included in the results. To 299 | find exact class instances, add a `*` prefix, for example: *UIScrollView. 300 | 301 | Advanced examples: 302 | 303 | # Find views that are either: hidden, invisible, or not in a window 304 | findinstances UIView hidden == true || alpha == 0 || window == nil 305 | # Find views that have either a zero width or zero height 306 | findinstances UIView layer.bounds.#size.width == 0 || layer.bounds.#size.height == 0 307 | # Find leaf views that have no subviews 308 | findinstances UIView subviews.@count == 0 309 | # Find dictionaries that have keys that might be passwords or passphrases 310 | findinstances NSDictionary any @allKeys beginswith 'pass' 311 | 312 | These examples make use of a filter. The filter is implemented with 313 | NSPredicate, see its documentaiton for more details. Basic NSPredicate 314 | expressions have relatively predicatable syntax. There are some exceptions 315 | as seen above, see https://github.com/facebook/chisel/wiki/findinstances. 316 | """ 317 | 318 | def lex(self, commandLine): 319 | # Can't use default shlex splitting because it strips quotes, which results 320 | # in invalid NSPredicate syntax. Split the input into type and rest (query). 321 | return commandLine.split(' ', 1) 322 | 323 | def run(self, arguments, options): 324 | if not self.loadChiselIfNecessary(): 325 | return 326 | 327 | if len(arguments) == 0 or not arguments[0].strip(): 328 | print ("Usage: findinstances []; Run `help findinstances`") 329 | return 330 | 331 | query = arguments[0] 332 | predicate = arguments[1].strip() 333 | # Escape double quotes and backslashes. 334 | predicate = re.sub('([\\"])', r'\\\1', predicate) 335 | call = '(void)PrintInstances("{}", "{}")'.format(query, predicate) 336 | fb.evaluateExpressionValue(call) 337 | 338 | def loadChiselIfNecessary(self): 339 | target = lldb.debugger.GetSelectedTarget() 340 | symbol_contexts = target.FindSymbols('PrintInstances', lldb.eSymbolTypeCode) 341 | if any(ctx.symbol.IsValid() for ctx in symbol_contexts): 342 | return True 343 | 344 | path = self.chiselLibraryPath() 345 | if not os.path.exists(path): 346 | print ('Chisel library missing: %s' %(path)) 347 | return False 348 | 349 | module = fb.evaluateExpressionValue('(void*)dlopen("{}", 2)'.format(path)) 350 | if module.unsigned != 0 or target.module['Chisel']: 351 | return True 352 | 353 | # `errno` is a macro that expands to a call to __error(). In development, 354 | # lldb was not getting a correct value for `errno`, so `__error()` is used. 355 | errno = fb.evaluateExpressionValue('*(int*)__error()').value 356 | error = fb.evaluateExpressionValue('(char*)dlerror()') 357 | if errno == 50: 358 | # KERN_CODESIGN_ERROR from 359 | print ('Error loading Chisel: Code signing failure; Must re-run codesign') 360 | elif error.unsigned != 0: 361 | print ('Error loading Chisel: %s' %(error.summary)) 362 | elif errno != 0: 363 | error = fb.evaluateExpressionValue('(char*)strerror({})'.format(errno)) 364 | if error.unsigned != 0: 365 | print ('Error loading Chisel: %s' %(error.summary)) 366 | else: 367 | print ('Error loading Chisel (errno {})'.format(errno)) 368 | else: 369 | print ('Unknown error loading Chisel') 370 | 371 | return False 372 | 373 | def chiselLibraryPath(self): 374 | # script os.environ['CHISEL_LIBRARY_PATH'] = '/path/to/custom/Chisel' 375 | path = os.getenv('CHISEL_LIBRARY_PATH') 376 | if path and os.path.exists(path): 377 | return path 378 | 379 | source_path = sys.modules[__name__].__file__ 380 | source_dir = os.path.dirname(source_path) 381 | # ugh: ../.. is to back out of commands/, then back out of libexec/ 382 | return os.path.join(source_dir, '..', '..', 'lib', 'Chisel.framework', 'Chisel') 383 | 384 | 385 | class FBHeapFromCommand(fb.FBCommand): 386 | def name(self): 387 | return 'heapfrom' 388 | 389 | def description(self): 390 | return 'Show all nested heap pointers contained within a given variable.' 391 | 392 | def run(self, arguments, options): 393 | # This command is like `expression --synthetic-type false`, except only showing nested heap references. 394 | var = self.context.frame.var(arguments[0]) 395 | if not var or not var.IsValid(): 396 | self.result.SetError('No variable named "{}"'.format(arguments[0])) 397 | return 398 | 399 | # Use the actual underlying structure of the variable, not the human friendly (synthetic) one. 400 | root = var.GetNonSyntheticValue() 401 | 402 | # Traversal of SBValue tree to get leaf nodes, which is where heap pointers will be. 403 | leafs = [] 404 | queue = [root] 405 | while queue: 406 | node = queue.pop(0) 407 | if node.num_children == 0: 408 | leafs.append(node) 409 | else: 410 | queue += [node.GetChildAtIndex(i) for i in range(node.num_children)] 411 | 412 | pointers = {} 413 | for node in leafs: 414 | # Assumption: an addr that has no value means a pointer. 415 | if node.addr and not node.value: 416 | pointers[node.load_addr] = node.path 417 | 418 | options = lldb.SBExpressionOptions() 419 | options.SetLanguage(lldb.eLanguageTypeC) 420 | def isHeap(addr): 421 | lookup = '(int)malloc_size({})'.format(addr) 422 | return self.context.frame.EvaluateExpression(lookup, options).unsigned != 0 423 | 424 | allocations = (addr for addr in pointers if isHeap(addr)) 425 | for addr in allocations: 426 | print >>self.result, '0x{addr:x} {path}'.format(addr=addr, path=pointers[addr]) 427 | if not allocations: 428 | print >>self.result, "No heap addresses found" 429 | 430 | 431 | class FBSequenceCommand(fb.FBCommand): 432 | def name(self): 433 | return 'sequence' 434 | 435 | def description(self): 436 | return 'Run commands in sequence, stopping on any error.' 437 | 438 | def lex(self, commandLine): 439 | return commandLine.split(';') 440 | 441 | def run(self, arguments, options): 442 | interpreter = lldb.debugger.GetCommandInterpreter() 443 | # The full unsplit command is in position 0. 444 | sequence = arguments[1:] 445 | for command in sequence: 446 | command = command.strip() 447 | if not command: 448 | continue 449 | object = lldb.SBCommandReturnObject() 450 | interpreter.HandleCommand(command, self.context, object) 451 | if object.GetOutput(): 452 | print >>self.result, object.GetOutput().strip() 453 | 454 | if not object.Succeeded(): 455 | if object.GetError(): 456 | self.result.SetError(object.GetError()) 457 | self.result.SetStatus(object.GetStatus()) 458 | return 459 | -------------------------------------------------------------------------------- /libexec/commands/FBDelay.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | from threading import Timer 3 | import fblldbbase as fb 4 | import fblldbobjcruntimehelpers as runtimeHelpers 5 | import lldb 6 | import string 7 | 8 | 9 | def lldbcommands(): 10 | return [ 11 | FBDelay() 12 | ] 13 | 14 | class FBDelay(fb.FBCommand): 15 | def name(self): 16 | return 'zzz' 17 | 18 | def description(self): 19 | return 'Executes specified lldb command after delay.' 20 | 21 | def args(self): 22 | return [ 23 | fb.FBCommandArgument(arg='delay in seconds', type='float', help='time to wait before executing specified command'), 24 | fb.FBCommandArgument(arg='lldb command', type='string', help='another lldb command to execute after specified delay', default='process interrupt') 25 | ] 26 | 27 | def run(self, arguments, options): 28 | lldb.debugger.SetAsync(True) 29 | lldb.debugger.HandleCommand('process continue') 30 | delay = float(arguments[0]) 31 | command = str(arguments[1]) 32 | t = Timer(delay, lambda: self.runDelayed(command)) 33 | t.start() 34 | 35 | def runDelayed(self, command): 36 | lldb.debugger.HandleCommand('process interrupt') 37 | lldb.debugger.HandleCommand(command) 38 | -------------------------------------------------------------------------------- /libexec/commands/FBDisplayCommands.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Copyright (c) 2014, Facebook, Inc. 4 | # All rights reserved. 5 | # 6 | # This source code is licensed under the BSD-style license found in the 7 | # LICENSE file in the root directory of this source tree. An additional grant 8 | # of patent rights can be found in the PATENTS file in the same directory. 9 | 10 | import lldb 11 | 12 | import fblldbviewhelpers as viewHelpers 13 | import fblldbviewcontrollerhelpers as viewControllerHelpers 14 | import fblldbbase as fb 15 | import fblldbobjcruntimehelpers as runtimeHelpers 16 | 17 | def lldbcommands(): 18 | return [ 19 | FBCoreAnimationFlushCommand(), 20 | FBDrawBorderCommand(), 21 | FBRemoveBorderCommand(), 22 | FBMaskViewCommand(), 23 | FBUnmaskViewCommand(), 24 | FBShowViewCommand(), 25 | FBHideViewCommand(), 26 | FBPresentViewControllerCommand(), 27 | FBDismissViewControllerCommand(), 28 | FBSlowAnimationCommand(), 29 | FBUnslowAnimationCommand() 30 | ] 31 | 32 | 33 | class FBDrawBorderCommand(fb.FBCommand): 34 | colors = [ 35 | "black", 36 | "gray", 37 | "red", 38 | "green", 39 | "blue", 40 | "cyan", 41 | "yellow", 42 | "magenta", 43 | "orange", 44 | "purple", 45 | "brown", 46 | ] 47 | 48 | def name(self): 49 | return 'border' 50 | 51 | def description(self): 52 | return 'Draws a border around . Color and width can be optionally provided. Additionally depth can be provided in order to recursively border subviews.' 53 | 54 | def args(self): 55 | return [ fb.FBCommandArgument(arg='viewOrLayer', type='UIView/NSView/CALayer *', help='The view/layer to border. NSViews must be layer-backed.') ] 56 | 57 | def options(self): 58 | return [ 59 | fb.FBCommandArgument(short='-c', long='--color', arg='color', type='string', default='red', help='A color name such as \'red\', \'green\', \'magenta\', etc.'), 60 | fb.FBCommandArgument(short='-w', long='--width', arg='width', type='CGFloat', default=2.0, help='Desired width of border.'), 61 | fb.FBCommandArgument(short='-d', long='--depth', arg='depth', type='int', default=0, help='Number of levels of subviews to border. Each level gets a different color beginning with the provided or default color'), 62 | ] 63 | 64 | def run(self, args, options): 65 | def setBorder(layer, width, color, colorClass): 66 | fb.evaluateEffect('[%s setBorderWidth:(CGFloat)%s]' % (layer, width)) 67 | fb.evaluateEffect('[%s setBorderColor:(CGColorRef)[(id)[%s %sColor] CGColor]]' % (layer, colorClass, color)) 68 | 69 | obj = fb.evaluateInputExpression(args[0]) 70 | depth = int(options.depth) 71 | isMac = runtimeHelpers.isMacintoshArch() 72 | color = options.color 73 | assert color in self.colors, "Color must be one of the following: {}".format(" ".join(self.colors)) 74 | colorClassName = 'UIColor' 75 | if isMac: 76 | colorClassName = 'NSColor' 77 | 78 | if viewHelpers.isView(obj): 79 | prevLevel = 0 80 | for view, level in viewHelpers.subviewsOfView(obj): 81 | if level > depth: 82 | break 83 | if prevLevel != level: 84 | color = self.nextColorAfterColor(color) 85 | prevLevel = level 86 | layer = viewHelpers.convertToLayer(view) 87 | setBorder(layer, options.width, color, colorClassName) 88 | else: 89 | # `obj` is not a view, make sure recursive bordering is not requested 90 | assert depth <= 0, "Recursive bordering is only supported for UIViews or NSViews" 91 | layer = viewHelpers.convertToLayer(obj) 92 | setBorder(layer, options.width, color, colorClassName) 93 | 94 | lldb.debugger.HandleCommand('caflush') 95 | 96 | def nextColorAfterColor(self, color): 97 | assert color in self.colors, "{} is not a supported color".format(color) 98 | return self.colors[(self.colors.index(color)+1) % len(self.colors)] 99 | 100 | class FBRemoveBorderCommand(fb.FBCommand): 101 | def name(self): 102 | return 'unborder' 103 | 104 | def description(self): 105 | return 'Removes border around .' 106 | 107 | def options(self): 108 | return [ 109 | fb.FBCommandArgument(short='-d', long='--depth', arg='depth', type='int', default=0, help='Number of levels of subviews to unborder.') 110 | ] 111 | 112 | def args(self): 113 | return [ fb.FBCommandArgument(arg='viewOrLayer', type='UIView/NSView/CALayer *', help='The view/layer to unborder.') ] 114 | 115 | def run(self, args, options): 116 | def setUnborder(layer): 117 | fb.evaluateEffect('[%s setBorderWidth:(CGFloat)%s]' % (layer, 0)) 118 | 119 | obj = args[0] 120 | depth = int(options.depth) 121 | if viewHelpers.isView(obj): 122 | for view, level in viewHelpers.subviewsOfView(obj): 123 | if level > depth: 124 | break 125 | layer = viewHelpers.convertToLayer(view) 126 | setUnborder(layer) 127 | else: 128 | # `obj` is not a view, make sure recursive unbordering is not requested 129 | assert depth <= 0, "Recursive unbordering is only supported for UIViews or NSViews" 130 | layer = viewHelpers.convertToLayer(obj) 131 | setUnborder(layer) 132 | 133 | lldb.debugger.HandleCommand('caflush') 134 | 135 | class FBMaskViewCommand(fb.FBCommand): 136 | def name(self): 137 | return 'mask' 138 | 139 | def description(self): 140 | return 'Add a transparent rectangle to the window to reveal a possibly obscured or hidden view or layer\'s bounds' 141 | 142 | def args(self): 143 | return [ fb.FBCommandArgument(arg='viewOrLayer', type='UIView/NSView/CALayer *', help='The view/layer to mask.') ] 144 | 145 | def options(self): 146 | return [ 147 | fb.FBCommandArgument(short='-c', long='--color', arg='color', type='string', default='red', help='A color name such as \'red\', \'green\', \'magenta\', etc.'), 148 | fb.FBCommandArgument(short='-a', long='--alpha', arg='alpha', type='CGFloat', default=0.5, help='Desired alpha of mask.') 149 | ] 150 | 151 | def run(self, args, options): 152 | viewOrLayer = fb.evaluateObjectExpression(args[0]) 153 | viewHelpers.maskView(viewOrLayer, options.color, options.alpha) 154 | 155 | 156 | class FBUnmaskViewCommand(fb.FBCommand): 157 | def name(self): 158 | return 'unmask' 159 | 160 | def description(self): 161 | return 'Remove mask from a view or layer' 162 | 163 | def args(self): 164 | return [ fb.FBCommandArgument(arg='viewOrLayer', type='UIView/CALayer *', help='The view/layer to mask.') ] 165 | 166 | def run(self, args, options): 167 | viewOrLayer = fb.evaluateObjectExpression(args[0]) 168 | viewHelpers.unmaskView(viewOrLayer) 169 | 170 | 171 | class FBCoreAnimationFlushCommand(fb.FBCommand): 172 | def name(self): 173 | return 'caflush' 174 | 175 | def description(self): 176 | return 'Force Core Animation to flush. This will \'repaint\' the UI but also may mess with ongoing animations.' 177 | 178 | def run(self, arguments, options): 179 | viewHelpers.flushCoreAnimationTransaction() 180 | 181 | 182 | class FBShowViewCommand(fb.FBCommand): 183 | def name(self): 184 | return 'show' 185 | 186 | def description(self): 187 | return 'Show a view or layer.' 188 | 189 | def args(self): 190 | return [ fb.FBCommandArgument(arg='viewOrLayer', type='UIView/NSView/CALayer *', help='The view/layer to show.') ] 191 | 192 | def run(self, args, options): 193 | viewHelpers.setViewHidden(args[0], False) 194 | 195 | 196 | class FBHideViewCommand(fb.FBCommand): 197 | def name(self): 198 | return 'hide' 199 | 200 | def description(self): 201 | return 'Hide a view or layer.' 202 | 203 | def args(self): 204 | return [ fb.FBCommandArgument(arg='viewOrLayer', type='UIView/NSView/CALayer *', help='The view/layer to hide.') ] 205 | 206 | def run(self, args, options): 207 | viewHelpers.setViewHidden(args[0], True) 208 | 209 | 210 | class FBPresentViewControllerCommand(fb.FBCommand): 211 | def name(self): 212 | return 'present' 213 | 214 | def description(self): 215 | return 'Present a view controller.' 216 | 217 | def args(self): 218 | return [ fb.FBCommandArgument(arg='viewController', type='UIViewController *', help='The view controller to present.') ] 219 | 220 | def run(self, args, option): 221 | viewControllerHelpers.presentViewController(args[0]) 222 | 223 | 224 | class FBDismissViewControllerCommand(fb.FBCommand): 225 | def name(self): 226 | return 'dismiss' 227 | 228 | def description(self): 229 | return 'Dismiss a presented view controller.' 230 | 231 | def args(self): 232 | return [ fb.FBCommandArgument(arg='viewController', type='UIViewController *', help='The view controller to dismiss.') ] 233 | 234 | def run(self, args, option): 235 | viewControllerHelpers.dismissViewController(args[0]) 236 | 237 | 238 | class FBSlowAnimationCommand(fb.FBCommand): 239 | def name(self): 240 | return 'slowanim' 241 | 242 | def description(self): 243 | return 'Slows down animations. Works on the iOS Simulator and a device.' 244 | 245 | def args(self): 246 | return [ fb.FBCommandArgument(arg='speed', type='float', default=0.1, help='Animation speed (default 0.1).') ] 247 | 248 | def run(self, args, option): 249 | viewHelpers.slowAnimation(args[0]) 250 | 251 | 252 | class FBUnslowAnimationCommand(fb.FBCommand): 253 | def name(self): 254 | return 'unslowanim' 255 | 256 | def description(self): 257 | return 'Turn off slow animations.' 258 | 259 | def run(self, args, option): 260 | viewHelpers.slowAnimation() 261 | -------------------------------------------------------------------------------- /libexec/commands/FBFindCommands.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Copyright (c) 2014, Facebook, Inc. 4 | # All rights reserved. 5 | # 6 | # This source code is licensed under the BSD-style license found in the 7 | # LICENSE file in the root directory of this source tree. An additional grant 8 | # of patent rights can be found in the PATENTS file in the same directory. 9 | 10 | import os 11 | import re 12 | 13 | import lldb 14 | import fblldbbase as fb 15 | import fblldbobjcruntimehelpers as objc 16 | import fblldbviewcontrollerhelpers as vcHelpers 17 | 18 | def lldbcommands(): 19 | return [ 20 | FBFindViewControllerCommand(), 21 | FBFindViewCommand(), 22 | FBTapLoggerCommand(), 23 | ] 24 | 25 | class FBFindViewControllerCommand(fb.FBCommand): 26 | def name(self): 27 | return 'fvc' 28 | 29 | def description(self): 30 | return 'Find the view controllers whose class names match classNameRegex and puts the address of first on the clipboard.' 31 | 32 | def options(self): 33 | return [ 34 | fb.FBCommandArgument(short='-n', long='--name', arg='classNameRegex', type='string', help='The view-controller-class regex to search the view controller hierarchy for.'), 35 | fb.FBCommandArgument(short='-v', long='--view', arg='view', type='UIView', help='This function will print the View Controller that owns this view.') 36 | ] 37 | 38 | def run(self, arguments, options): 39 | if options.classNameRegex and options.view: 40 | print("Do not set both the --name and --view flags") 41 | elif options.view: 42 | self.findOwningViewController(options.view) 43 | else: 44 | output = vcHelpers.viewControllerRecursiveDescription('(id)[[[UIApplication sharedApplication] keyWindow] rootViewController]') 45 | searchString = options.classNameRegex if options.classNameRegex else arguments[0] 46 | printMatchesInViewOutputStringAndCopyFirstToClipboard(searchString, output) 47 | 48 | def findOwningViewController(self, object): 49 | while object: 50 | if self.isViewController(object): 51 | description = fb.evaluateExpressionValue(object).GetObjectDescription() 52 | print("Found the owning view controller.\n{}".format(description)) 53 | cmd = 'echo {} | tr -d "\n" | pbcopy'.format(object) 54 | os.system(cmd) 55 | return 56 | else: 57 | object = self.nextResponder(object) 58 | print("Could not find an owning view controller") 59 | 60 | @staticmethod 61 | def isViewController(object): 62 | command = '[(id){} isKindOfClass:[UIViewController class]]'.format(object) 63 | isVC = fb.evaluateBooleanExpression(command) 64 | return isVC 65 | 66 | @staticmethod 67 | def nextResponder(object): 68 | command = '[((id){}) nextResponder]'.format(object) 69 | nextResponder = fb.evaluateObjectExpression(command) 70 | try: 71 | if int(nextResponder, 0): 72 | return nextResponder 73 | else: 74 | return None 75 | except: 76 | return None 77 | 78 | 79 | class FBFindViewCommand(fb.FBCommand): 80 | def name(self): 81 | return 'fv' 82 | 83 | def description(self): 84 | return 'Find the views whose class names match classNameRegex and puts the address of first on the clipboard.' 85 | 86 | def args(self): 87 | return [ fb.FBCommandArgument(arg='classNameRegex', type='string', help='The view-class regex to search the view hierarchy for.') ] 88 | 89 | def run(self, arguments, options): 90 | output = fb.evaluateExpressionValue('(id)[[[UIApplication sharedApplication] keyWindow] recursiveDescription]').GetObjectDescription() 91 | printMatchesInViewOutputStringAndCopyFirstToClipboard(arguments[0], output) 92 | 93 | 94 | def printMatchesInViewOutputStringAndCopyFirstToClipboard(needle, haystack): 95 | first = None 96 | for match in re.finditer('.*<.*(' + needle + ')\\S*: (0x[0-9a-fA-F]*);.*', haystack, re.IGNORECASE): 97 | view = match.groups()[-1] 98 | className = fb.evaluateExpressionValue('(id)[(' + view + ') class]').GetObjectDescription() 99 | print('{} {}'.format(view, className)) 100 | if first is None: 101 | first = view 102 | cmd = 'echo %s | tr -d "\n" | pbcopy' % view 103 | os.system(cmd) 104 | 105 | 106 | class FBTapLoggerCommand(fb.FBCommand): 107 | def name(self): 108 | return 'taplog' 109 | 110 | def description(self): 111 | return 'Log tapped view to the console.' 112 | 113 | def run(self, arguments, options): 114 | parameterExpr = objc.functionPreambleExpressionForObjectParameterAtIndex(0) 115 | breakpoint = lldb.debugger.GetSelectedTarget().BreakpointCreateByName("-[UIApplication sendEvent:]") 116 | breakpoint.SetCondition('(int)[' + parameterExpr + ' type] == 0 && (int)[[[' + parameterExpr + ' allTouches] anyObject] phase] == 0') 117 | breakpoint.SetOneShot(True) 118 | lldb.debugger.HandleCommand('breakpoint command add -s python -F "sys.modules[\'' + __name__ + '\'].' + self.__class__.__name__ + '.taplog_callback" ' + str(breakpoint.id)) 119 | lldb.debugger.SetAsync(True) 120 | lldb.debugger.HandleCommand('continue') 121 | 122 | @staticmethod 123 | def taplog_callback(frame, bp_loc, internal_dict): 124 | parameterExpr = objc.functionPreambleExpressionForObjectParameterAtIndex(0) 125 | print (fb.describeObject('[[[%s allTouches] anyObject] view]' % (parameterExpr))) 126 | # We don't want to proceed event (click on button for example), so we just skip it 127 | lldb.debugger.HandleCommand('thread return') 128 | -------------------------------------------------------------------------------- /libexec/commands/FBFlickerCommands.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Copyright (c) 2014, Facebook, Inc. 4 | # All rights reserved. 5 | # 6 | # This source code is licensed under the BSD-style license found in the 7 | # LICENSE file in the root directory of this source tree. An additional grant 8 | # of patent rights can be found in the PATENTS file in the same directory. 9 | 10 | import os 11 | import sys 12 | 13 | import lldb 14 | import fblldbbase as fb 15 | import fblldbviewhelpers as viewHelpers 16 | import fblldbobjcruntimehelpers as runtimeHelpers 17 | 18 | def lldbcommands(): 19 | return [ 20 | FBFlickerViewCommand(), 21 | FBViewSearchCommand(), 22 | ] 23 | 24 | 25 | class FBFlickerViewCommand(fb.FBCommand): 26 | def name(self): 27 | return 'flicker' 28 | 29 | def description(self): 30 | return 'Quickly show and hide a view to quickly help visualize where it is.' 31 | 32 | def args(self): 33 | return [ fb.FBCommandArgument(arg='viewOrLayer', type='UIView/NSView*', help='The view to flicker.') ] 34 | 35 | def run(self, arguments, options): 36 | object = fb.evaluateObjectExpression(arguments[0]) 37 | 38 | isHidden = fb.evaluateBooleanExpression('[' + object + ' isHidden]') 39 | shouldHide = not isHidden 40 | for x in range(0, 2): 41 | viewHelpers.setViewHidden(object, shouldHide) 42 | viewHelpers.setViewHidden(object, isHidden) 43 | 44 | 45 | class FBViewSearchCommand(fb.FBCommand): 46 | def name(self): 47 | return 'vs' 48 | 49 | def description(self): 50 | return 'Interactively search for a view by walking the hierarchy.' 51 | 52 | def args(self): 53 | return [ fb.FBCommandArgument(arg='view', type='UIView*', help='The view to border.') ] 54 | 55 | def run(self, arguments, options): 56 | print ('\nUse the following and (q) to quit.\n(w) move to superview\n(s) move to first subview\n(a) move to previous sibling\n(d) move to next sibling\n(p) print the hierarchy\n') 57 | 58 | object = fb.evaluateInputExpression(arguments[0]) 59 | walker = FlickerWalker(object) 60 | walker.run() 61 | 62 | class FlickerWalker: 63 | def __init__(self, startView): 64 | self.setCurrentView(startView) 65 | 66 | def run(self): 67 | self.keepRunning = True 68 | initialAsync = lldb.debugger.GetAsync() 69 | lldb.debugger.SetAsync(True) #Needed so XCode doesn't hang if tap on Continue while lldb is waiting for user input in 'vs' mode 70 | while self.keepRunning: 71 | charRead = sys.stdin.readline().rstrip("\n") 72 | self.inputCallback(charRead) 73 | else: 74 | lldb.debugger.SetAsync(initialAsync) 75 | 76 | def inputCallback(self, input): 77 | oldView = self.currentView 78 | 79 | if input == 'q': 80 | cmd = 'echo %s | tr -d "\n" | pbcopy' % oldView 81 | os.system(cmd) 82 | 83 | print ('\nI hope ' + oldView + ' was what you were looking for. I put it on your clipboard.') 84 | viewHelpers.unmaskView(oldView) 85 | self.keepRunning = False 86 | 87 | elif input == 'w': 88 | v = superviewOfView(self.currentView) 89 | if not v: 90 | print ('There is no superview. Where are you trying to go?!') 91 | self.setCurrentView(v, oldView) 92 | elif input == 's': 93 | v = firstSubviewOfView(self.currentView) 94 | if not v: 95 | print ('\nThe view has no subviews.\n') 96 | self.setCurrentView(v, oldView) 97 | elif input == 'd': 98 | v = nthSiblingOfView(self.currentView, -1) 99 | if v == oldView: 100 | print ('\nThere are no sibling views to this view.\n') 101 | self.setCurrentView(v, oldView) 102 | elif input == 'a': 103 | v = nthSiblingOfView(self.currentView, 1) 104 | if v == oldView: 105 | print ('\nThere are no sibling views to this view.\n') 106 | self.setCurrentView(v, oldView) 107 | elif input == 'p': 108 | recursionName = 'recursiveDescription' 109 | isMac = runtimeHelpers.isMacintoshArch() 110 | 111 | if isMac: 112 | recursionName = '_subtreeDescription' 113 | 114 | print (fb.describeObject('[(id){} {}]'.format(oldView, recursionName))) 115 | else: 116 | print ('\nI really have no idea what you meant by \'' + input + '\'... =\\\n') 117 | 118 | def setCurrentView(self, view, oldView=None): 119 | if view: 120 | self.currentView = view 121 | if oldView: 122 | viewHelpers.unmaskView(oldView) 123 | viewHelpers.maskView(self.currentView, 'red', '0.4') 124 | print (fb.describeObject(view)) 125 | 126 | def superviewOfView(view): 127 | superview = fb.evaluateObjectExpression('[' + view + ' superview]') 128 | if int(superview, 16) == 0: 129 | return None 130 | 131 | return superview 132 | 133 | def subviewsOfView(view): 134 | return fb.evaluateObjectExpression('[' + view + ' subviews]') 135 | 136 | def firstSubviewOfView(view): 137 | subviews = subviewsOfView(view) 138 | numViews = fb.evaluateIntegerExpression('[(id)' + subviews + ' count]') 139 | 140 | if numViews == 0: 141 | return None 142 | else: 143 | return fb.evaluateObjectExpression('[' + subviews + ' objectAtIndex:0]') 144 | 145 | def nthSiblingOfView(view, n): 146 | subviews = subviewsOfView(superviewOfView(view)) 147 | numViews = fb.evaluateIntegerExpression('[(id)' + subviews + ' count]') 148 | 149 | idx = fb.evaluateIntegerExpression('[(id)' + subviews + ' indexOfObject:' + view + ']') 150 | 151 | newIdx = idx + n 152 | while newIdx < 0: 153 | newIdx += numViews 154 | newIdx = newIdx % numViews 155 | 156 | return fb.evaluateObjectExpression('[(id)' + subviews + ' objectAtIndex:' + str(newIdx) + ']') 157 | -------------------------------------------------------------------------------- /libexec/commands/FBInvocationCommands.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Copyright (c) 2014, Facebook, Inc. 4 | # All rights reserved. 5 | # 6 | # This source code is licensed under the BSD-style license found in the 7 | # LICENSE file in the root directory of this source tree. An additional grant 8 | # of patent rights can be found in the PATENTS file in the same directory. 9 | 10 | import re 11 | 12 | import lldb 13 | import fblldbbase as fb 14 | 15 | def lldbcommands(): 16 | return [ 17 | FBPrintInvocation(), 18 | ] 19 | 20 | class FBPrintInvocation(fb.FBCommand): 21 | def name(self): 22 | return 'pinvocation' 23 | 24 | def description(self): 25 | return 'Print the stack frame, receiver, and arguments of the current invocation. It will fail to print all arguments if any arguments are variadic (varargs).\n\nNOTE: Sadly this is currently only implemented on x86.' 26 | 27 | def options(self): 28 | return [ 29 | fb.FBCommandArgument(short='-a', long='--all', arg='all', default=False, boolean=True, help='Specify to print the entire stack instead of just the current frame.'), 30 | ] 31 | 32 | def run(self, arguments, options): 33 | target = lldb.debugger.GetSelectedTarget() 34 | 35 | if not re.match(r'.*i386.*', target.GetTriple()): 36 | print ('Only x86 is currently supported (32-bit iOS Simulator or Mac OS X).') 37 | return 38 | 39 | thread = target.GetProcess().GetSelectedThread() 40 | 41 | if options.all: 42 | for frame in thread: 43 | printInvocationForFrame(frame) 44 | print ('---------------------------------') 45 | else: 46 | frame = thread.GetSelectedFrame() 47 | printInvocationForFrame(frame) 48 | 49 | def printInvocationForFrame(frame): 50 | print (frame) 51 | 52 | symbolName = frame.GetSymbol().GetName() 53 | if not re.match(r'[-+]\s*\[.*\]', symbolName): 54 | return 55 | 56 | self = findArgAtIndexFromStackFrame(frame, 0) 57 | cmd = findArgAtIndexFromStackFrame(frame, 1) 58 | 59 | commandForSignature = '[(id)' + self + ' methodSignatureForSelector:(char *)sel_getName((SEL)' + cmd + ')]' 60 | signatureValue = fb.evaluateExpressionValue('(id)' + commandForSignature) 61 | 62 | if signatureValue.GetError() is not None and str(signatureValue.GetError()) != 'success': 63 | print ("My sincerest apologies. I couldn't find a method signature for the selector.") 64 | return 65 | 66 | signature = signatureValue.GetValue() 67 | 68 | arg0 = stackStartAddressInSelectedFrame(frame) 69 | commandForInvocation = '[NSInvocation _invocationWithMethodSignature:(id)' + signature + ' frame:((void *)' + str(arg0) + ')]' 70 | invocation = fb.evaluateExpression('(id)' + commandForInvocation) 71 | 72 | if invocation: 73 | prettyPrintInvocation(frame, invocation) 74 | else: 75 | print (frame) 76 | 77 | def stackStartAddressInSelectedFrame(frame): 78 | # Determine if the %ebp register has already had the stack register pushed into it (always the first instruction) 79 | frameSymbol = frame.GetSymbolContext(0).GetSymbol() 80 | frameStartAddress = frameSymbol.GetStartAddress().GetLoadAddress(lldb.debugger.GetSelectedTarget()) 81 | 82 | currentPC = frame.GetPC() 83 | 84 | offset = currentPC - frameStartAddress 85 | 86 | if offset == 0: 87 | return int(frame.EvaluateExpression('($esp + 4)').GetValue()) 88 | elif offset == 1: 89 | return int(frame.EvaluateExpression('($esp + 8)').GetValue()) 90 | else: 91 | return int(frame.EvaluateExpression('($ebp + 8)').GetValue()) 92 | 93 | 94 | def findArgAtIndexFromStackFrame(frame, index): 95 | return fb.evaluateExpression('*(int *)' + str(findArgAdressAtIndexFromStackFrame(frame, index))) 96 | 97 | def findArgAdressAtIndexFromStackFrame(frame, index): 98 | arg0 = stackStartAddressInSelectedFrame(frame) 99 | arg = arg0 + 4 * index 100 | return arg 101 | 102 | def prettyPrintInvocation(frame, invocation): 103 | object = fb.evaluateExpression('(id)[(id)' + invocation + ' target]') 104 | description = fb.evaluateExpressionValue('(id)' + invocation).GetObjectDescription() 105 | argDescriptions = description.splitlines(True)[4:] 106 | 107 | print ('NSInvocation: ' + invocation) 108 | print ('self: ' + fb.evaluateExpression('(id)' + object)) 109 | 110 | if len(argDescriptions) > 0: 111 | print ('\n' + str(len(argDescriptions)) + ' Arguments:' if len(argDescriptions) > 1 else '\nArgument:') 112 | 113 | index = 2 114 | for argDescription in argDescriptions: 115 | s = re.sub(r'argument [0-9]+: ', '', argDescription) 116 | 117 | address = findArgAdressAtIndexFromStackFrame(frame, index) 118 | 119 | encoding = s.split(' ')[0] 120 | description = ' '.join(s.split(' ')[1:]) 121 | 122 | readableString = argumentAsString(frame, address, encoding) 123 | 124 | if readableString: 125 | print (readableString) 126 | else: 127 | if encoding[0] == '{': 128 | encoding = encoding[1:] 129 | print ((hex(address) + ', address of ' + encoding + ' ' + description).strip()) 130 | 131 | index += 1 132 | 133 | def argumentAsString(frame, address, encoding): 134 | if encoding[0] == '{': 135 | encoding = encoding[1:] 136 | 137 | encodingMap = { 138 | 'c': 'char', 139 | 'i': 'int', 140 | 's': 'short', 141 | 'l': 'long', 142 | 'q': 'long long', 143 | 144 | 'C': 'unsigned char', 145 | 'I': 'unsigned int', 146 | 'S': 'unsigned short', 147 | 'L': 'unsigned long', 148 | 'Q': 'unsigned long long', 149 | 150 | 'f': 'float', 151 | 'd': 'double', 152 | 'B': 'bool', 153 | 'v': 'void', 154 | '*': 'char *', 155 | '@': 'id', 156 | '#': 'Class', 157 | ':': 'SEL', 158 | } 159 | 160 | pointers = '' 161 | while encoding[0] == '^': 162 | pointers += '*' 163 | encoding = encoding[1:] 164 | 165 | type = None 166 | if encoding in encodingMap: 167 | type = encodingMap[encoding] 168 | 169 | if type and pointers: 170 | type = type + ' ' + pointers 171 | 172 | if not type: 173 | # Handle simple structs: {CGPoint=ff}, {CGSize=ff}, {CGRect={CGPoint=ff}{CGSize=ff}} 174 | if encoding[0] == '{': 175 | encoding = encoding[1:] 176 | 177 | type = re.sub(r'=.*', '', encoding) 178 | if pointers: 179 | type += ' ' + pointers 180 | 181 | if type: 182 | value = frame.EvaluateExpression('*(' + type + ' *)' + str(address)) 183 | 184 | if value.GetError() is None or str(value.GetError()) == 'success': 185 | description = None 186 | 187 | if encoding == '@': 188 | description = value.GetObjectDescription() 189 | 190 | if not description: 191 | description = value.GetValue() 192 | if not description: 193 | description = value.GetSummary() 194 | if description: 195 | return type + ': ' + description 196 | 197 | return None 198 | -------------------------------------------------------------------------------- /libexec/commands/FBPrintCommands.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Copyright (c) 2014, Facebook, Inc. 4 | # All rights reserved. 5 | # 6 | # This source code is licensed under the BSD-style license found in the 7 | # LICENSE file in the root directory of this source tree. An additional grant 8 | # of patent rights can be found in the PATENTS file in the same directory. 9 | 10 | import os 11 | import re 12 | import subprocess 13 | 14 | import lldb 15 | import fblldbbase as fb 16 | import fblldbviewcontrollerhelpers as vcHelpers 17 | import fblldbviewhelpers as viewHelpers 18 | import fblldbobjcruntimehelpers as runtimeHelpers 19 | 20 | def lldbcommands(): 21 | return [ 22 | FBPrintViewHierarchyCommand(), 23 | FBPrintCoreAnimationTree(), 24 | FBPrintViewControllerHierarchyCommand(), 25 | FBPrintIsExecutingInAnimationBlockCommand(), 26 | FBPrintInheritanceHierarchy(), 27 | FBPrintUpwardResponderChain(), 28 | FBPrintOnscreenTableView(), 29 | FBPrintOnscreenTableViewCells(), 30 | FBPrintInternals(), 31 | FBPrintInstanceVariable(), 32 | FBPrintKeyPath(), 33 | FBPrintApplicationDocumentsPath(), 34 | FBPrintApplicationBundlePath(), 35 | FBPrintData(), 36 | FBPrintTargetActions(), 37 | FBPrintJSON(), 38 | FBPrintSwiftJSON(), 39 | FBPrintAsCurl(), 40 | FBPrintToClipboard(), 41 | FBPrintObjectInObjc(), 42 | ] 43 | 44 | class FBPrintViewHierarchyCommand(fb.FBCommand): 45 | def name(self): 46 | return 'pviews' 47 | 48 | def description(self): 49 | return 'Print the recursion description of .' 50 | 51 | def options(self): 52 | return [ 53 | fb.FBCommandArgument(short='-u', long='--up', arg='upwards', boolean=True, default=False, help='Print only the hierarchy directly above the view, up to its window.'), 54 | fb.FBCommandArgument(short='-d', long='--depth', arg='depth', type='int', default="0", help='Print only to a given depth. 0 indicates infinite depth.'), 55 | fb.FBCommandArgument(short='-w', long='--window', arg='window', type='int', default="0", help='Specify the window to print a description of. Check which windows exist with "po (id)[[UIApplication sharedApplication] windows]".'), 56 | ] 57 | 58 | def args(self): 59 | return [ fb.FBCommandArgument(arg='aView', type='UIView*/NSView*', help='The view to print the description of.', default='__keyWindow_dynamic__') ] 60 | 61 | def run(self, arguments, options): 62 | maxDepth = int(options.depth) 63 | window = int(options.window) 64 | isMac = runtimeHelpers.isMacintoshArch() 65 | 66 | if window > 0: 67 | if isMac: 68 | arguments[0] = '(id)[[[[NSApplication sharedApplication] windows] objectAtIndex:' + str(window) + '] contentView]' 69 | else: 70 | arguments[0] = '(id)[[[UIApplication sharedApplication] windows] objectAtIndex:' + str(window) + ']' 71 | elif arguments[0] == '__keyWindow_dynamic__': 72 | if isMac: 73 | arguments[0] = '(id)[[[[NSApplication sharedApplication] windows] objectAtIndex:0] contentView]' 74 | else: 75 | arguments[0] = '(id)[[UIApplication sharedApplication] keyWindow]' 76 | 77 | if options.upwards: 78 | view = arguments[0] 79 | description = viewHelpers.upwardsRecursiveDescription(view, maxDepth) 80 | if description: 81 | print (description) 82 | else: 83 | print ('Failed to walk view hierarchy. Make sure you pass a view, not any other kind of object or expression.') 84 | else: 85 | printingMethod = 'recursiveDescription' 86 | if isMac: 87 | printingMethod = '_subtreeDescription' 88 | 89 | description = fb.evaluateExpressionValue('(id)[' + arguments[0] + ' ' + printingMethod + ']').GetObjectDescription() 90 | if maxDepth > 0: 91 | separator = re.escape(" | ") 92 | prefixToRemove = separator * maxDepth + " " 93 | description += "\n" 94 | description = re.sub(r'%s.*\n' % (prefixToRemove), r'', description) 95 | print (description) 96 | 97 | class FBPrintCoreAnimationTree(fb.FBCommand): 98 | def name(self): 99 | return 'pca' 100 | 101 | def description(self): 102 | return 'Print layer tree from the perspective of the render server.' 103 | 104 | def run(self, arguments, options): 105 | print (fb.describeObject('[NSString stringWithCString:(char *)CARenderServerGetInfo(0, 2, 0)]')) 106 | 107 | 108 | class FBPrintViewControllerHierarchyCommand(fb.FBCommand): 109 | def name(self): 110 | return 'pvc' 111 | 112 | def description(self): 113 | return 'Print the recursion description of .' 114 | 115 | def args(self): 116 | return [ fb.FBCommandArgument(arg='aViewController', type='UIViewController*', help='The view controller to print the description of.', default='__keyWindow_rootVC_dynamic__') ] 117 | 118 | def run(self, arguments, options): 119 | isMac = runtimeHelpers.isMacintoshArch() 120 | 121 | if arguments[0] == '__keyWindow_rootVC_dynamic__': 122 | if fb.evaluateBooleanExpression('[UIViewController respondsToSelector:@selector(_printHierarchy)]'): 123 | print (fb.describeObject('[UIViewController _printHierarchy]')) 124 | return 125 | 126 | arguments[0] = '(id)[(id)[[UIApplication sharedApplication] keyWindow] rootViewController]' 127 | if isMac: 128 | arguments[0] = '(id)[[[[NSApplication sharedApplication] windows] objectAtIndex:0] contentViewController]' 129 | 130 | print (vcHelpers.viewControllerRecursiveDescription(arguments[0])) 131 | 132 | 133 | class FBPrintIsExecutingInAnimationBlockCommand(fb.FBCommand): 134 | def name(self): 135 | return 'panim' 136 | 137 | def description(self): 138 | return 'Prints if the code is currently execution with a UIView animation block.' 139 | 140 | def run(self, arguments, options): 141 | lldb.debugger.HandleCommand('p (BOOL)[UIView _isInAnimationBlock]') 142 | 143 | 144 | def _printIterative(initialValue, generator): 145 | indent = 0 146 | for currentValue in generator(initialValue): 147 | print (' | ' * indent + currentValue) 148 | indent += 1 149 | 150 | 151 | class FBPrintInheritanceHierarchy(fb.FBCommand): 152 | def name(self): 153 | return 'pclass' 154 | 155 | def description(self): 156 | return 'Print the inheritance starting from an instance of any class.' 157 | 158 | def args(self): 159 | return [ fb.FBCommandArgument(arg='object', type='id', help='The instance to examine.') ] 160 | 161 | def run(self, arguments, options): 162 | _printIterative(arguments[0], _inheritanceHierarchy) 163 | 164 | def _inheritanceHierarchy(instanceOfAClass): 165 | instanceAddress = fb.evaluateExpression(instanceOfAClass) 166 | instanceClass = fb.evaluateExpression('(id)[(id)' + instanceAddress + ' class]') 167 | while int(instanceClass, 16): 168 | yield fb.evaluateExpressionValue(instanceClass).GetObjectDescription() 169 | instanceClass = fb.evaluateExpression('(id)[(id)' + instanceClass + ' superclass]') 170 | 171 | 172 | class FBPrintUpwardResponderChain(fb.FBCommand): 173 | def name(self): 174 | return 'presponder' 175 | 176 | def description(self): 177 | return 'Print the responder chain starting from a specific responder.' 178 | 179 | def args(self): 180 | return [ fb.FBCommandArgument(arg='startResponder', type='UIResponder *', help='The responder to use to start walking the chain.') ] 181 | 182 | def run(self, arguments, options): 183 | startResponder = fb.evaluateInputExpression(arguments[0]) 184 | 185 | isMac = runtimeHelpers.isMacintoshArch() 186 | responderClass = 'UIResponder' 187 | if isMac: 188 | responderClass = 'NSResponder' 189 | 190 | if not fb.evaluateBooleanExpression('(BOOL)[(id)' + startResponder + ' isKindOfClass:[' + responderClass + ' class]]'): 191 | # print 'Whoa, ' + startResponder + ' is not a ' + responderClass + '. =(' 192 | print ("Whoa, %s is not a %s . =(" %(startResponder, responderClass)) 193 | return 194 | 195 | _printIterative(startResponder, _responderChain) 196 | 197 | def _responderChain(startResponder): 198 | responderAddress = fb.evaluateExpression(startResponder) 199 | while int(responderAddress, 16): 200 | yield fb.evaluateExpressionValue(responderAddress).GetObjectDescription() 201 | responderAddress = fb.evaluateExpression('(id)[(id)' + responderAddress + ' nextResponder]') 202 | 203 | 204 | def tableViewInHierarchy(): 205 | viewDescription = fb.evaluateExpressionValue('(id)[(id)[[UIApplication sharedApplication] keyWindow] recursiveDescription]').GetObjectDescription() 206 | 207 | searchView = None 208 | 209 | # Try to find an instance of 210 | classPattern = re.compile(r'UITableView: (0x[0-9a-fA-F]+);') 211 | for match in re.finditer(classPattern, viewDescription): 212 | searchView = match.group(1) 213 | break 214 | 215 | # Try to find a direct subclass 216 | if not searchView: 217 | subclassPattern = re.compile(r'(0x[0-9a-fA-F]+); baseClass = UITableView;') 218 | for match in re.finditer(subclassPattern, viewDescription): 219 | searchView = match.group(1) 220 | break 221 | 222 | # SLOW: check every pointer in town 223 | if not searchView: 224 | pattern = re.compile(r'(0x[0-9a-fA-F]+)[;>]') 225 | for (view) in re.findall(pattern, viewDescription): 226 | if fb.evaluateBooleanExpression('[' + view + ' isKindOfClass:(id)[UITableView class]]'): 227 | searchView = view 228 | break 229 | 230 | return searchView 231 | 232 | class FBPrintOnscreenTableView(fb.FBCommand): 233 | def name(self): 234 | return 'ptv' 235 | 236 | def description(self): 237 | return 'Print the highest table view in the hierarchy.' 238 | 239 | def run(self, arguments, options): 240 | tableView = tableViewInHierarchy() 241 | if tableView: 242 | viewValue = fb.evaluateExpressionValue(tableView) 243 | print (viewValue.GetObjectDescription()) 244 | cmd = 'echo %s | tr -d "\n" | pbcopy' % tableView 245 | os.system(cmd) 246 | else: 247 | print ('Sorry, chump. I couldn\'t find a table-view. :\'(') 248 | 249 | class FBPrintOnscreenTableViewCells(fb.FBCommand): 250 | def name(self): 251 | return 'pcells' 252 | 253 | def description(self): 254 | return 'Print the visible cells of the highest table view in the hierarchy.' 255 | 256 | def run(self, arguments, options): 257 | tableView = tableViewInHierarchy() 258 | print (fb.evaluateExpressionValue('(id)[(id)' + tableView + ' visibleCells]').GetObjectDescription()) 259 | 260 | 261 | class FBPrintInternals(fb.FBCommand): 262 | def name(self): 263 | return 'pinternals' 264 | 265 | def description(self): 266 | return 'Show the internals of an object by dereferencing it as a pointer.' 267 | 268 | def args(self): 269 | return [ fb.FBCommandArgument(arg='object', type='id', help='Object expression to be evaluated.') ] 270 | 271 | def options(self): 272 | return [ 273 | fb.FBCommandArgument(arg='appleWay', short='-a', long='--apple', boolean=True, default=False, help='Print ivars the apple way') 274 | ] 275 | 276 | def run(self, arguments, options): 277 | object = fb.evaluateObjectExpression(arguments[0]) 278 | if options.appleWay: 279 | if fb.evaluateBooleanExpression('[{} respondsToSelector:@selector(_ivarDescription)]'.format(object)): 280 | command = 'po [{} _ivarDescription]'.format(object) 281 | else: 282 | print ('Sorry, but it seems Apple dumped the _ivarDescription method') 283 | return 284 | else: 285 | objectClass = fb.evaluateExpressionValue('(id)[(id)(' + object + ') class]').GetObjectDescription() 286 | command = 'p *(({} *)((id){}))'.format(objectClass, object) 287 | lldb.debugger.HandleCommand(command) 288 | 289 | 290 | class FBPrintInstanceVariable(fb.FBCommand): 291 | def name(self): 292 | return 'pivar' 293 | 294 | def description(self): 295 | return "Print the value of an object's named instance variable." 296 | 297 | def args(self): 298 | return [ 299 | fb.FBCommandArgument(arg='object', type='id', help='Object expression to be evaluated.'), 300 | fb.FBCommandArgument(arg='ivarName', help='Name of instance variable to print.') 301 | ] 302 | 303 | def run(self, arguments, options): 304 | object = fb.evaluateInputExpression(arguments[0]) 305 | ivarName = arguments[1] 306 | 307 | objectClass = fb.evaluateExpressionValue('(id)[(' + object + ') class]').GetObjectDescription() 308 | 309 | ivarTypeCommand = '((char *)ivar_getTypeEncoding((void*)object_getInstanceVariable((id){}, \"{}\", 0)))[0]'.format(object, ivarName) 310 | ivarTypeEncodingFirstChar = fb.evaluateExpression(ivarTypeCommand) 311 | 312 | result = fb.evaluateExpressionValue('(({} *)({}))->{}'.format(objectClass, object, ivarName)) 313 | print (result.GetObjectDescription()) if '@' in ivarTypeEncodingFirstChar else result 314 | 315 | class FBPrintKeyPath(fb.FBCommand): 316 | def name(self): 317 | return 'pkp' 318 | 319 | def description(self): 320 | return "Print out the value of the key path expression using -valueForKeyPath:" 321 | 322 | def args(self): 323 | return [ 324 | fb.FBCommandArgument(arg='keypath', type='NSString *', help='The keypath to print'), 325 | ] 326 | 327 | def run(self, arguments, options): 328 | command = arguments[0] 329 | if len(command.split('.')) == 1: 330 | lldb.debugger.HandleCommand("po " + command) 331 | else: 332 | objectToMessage, keypath = command.split('.', 1) 333 | object = fb.evaluateObjectExpression(objectToMessage) 334 | print (fb.describeObject('[{} valueForKeyPath:@"{}"]'.format(object, keypath))) 335 | 336 | 337 | class FBPrintApplicationDocumentsPath(fb.FBCommand): 338 | def name(self): 339 | return 'pdocspath' 340 | 341 | def description(self): 342 | return "Print application's 'Documents' directory path." 343 | 344 | def options(self): 345 | return [ 346 | fb.FBCommandArgument(short='-o', long='--open', arg='open', boolean=True, default=False, help='open in Finder'), 347 | ] 348 | 349 | def run(self, arguments, options): 350 | # in iOS SDK NSDocumentDirectory == 9 NSUserDomainMask == 1 351 | NSDocumentDirectory = '9' 352 | NSUserDomainMask = '1' 353 | path = fb.evaluateExpressionValue('(NSString*)[NSSearchPathForDirectoriesInDomains(' + NSDocumentDirectory + ', ' + NSUserDomainMask + ', YES) lastObject]') 354 | pathString = '{}'.format(path).split('"')[1] 355 | cmd = 'echo {} | tr -d "\n" | pbcopy'.format(pathString) 356 | os.system(cmd) 357 | print (pathString) 358 | if options.open: 359 | os.system('open '+ pathString) 360 | 361 | 362 | class FBPrintApplicationBundlePath(fb.FBCommand): 363 | def name(self): 364 | return 'pbundlepath' 365 | 366 | def description(self): 367 | return "Print application's bundle directory path." 368 | 369 | def options(self): 370 | return [ 371 | fb.FBCommandArgument(short='-o', long='--open', arg='open', boolean=True, default=False, help='open in Finder'), 372 | ] 373 | 374 | def run(self, arguments, options): 375 | path = fb.evaluateExpressionValue('(NSString*)[[NSBundle mainBundle] bundlePath]') 376 | pathString = '{}'.format(path).split('"')[1] 377 | cmd = 'echo {} | tr -d "\n" | pbcopy'.format(pathString) 378 | os.system(cmd) 379 | print (pathString) 380 | if options.open: 381 | os.system('open '+ pathString) 382 | 383 | 384 | class FBPrintData(fb.FBCommand): 385 | def name(self): 386 | return 'pdata' 387 | 388 | def description(self): 389 | return 'Print the contents of NSData object as string.\n' \ 390 | 'Supported encodings:\n' \ 391 | '- ascii,\n' \ 392 | '- utf8,\n' \ 393 | '- utf16, unicode,\n' \ 394 | '- utf16l (Little endian),\n' \ 395 | '- utf16b (Big endian),\n' \ 396 | '- utf32,\n' \ 397 | '- utf32l (Little endian),\n' \ 398 | '- utf32b (Big endian),\n' \ 399 | '- latin1, iso88591 (88591),\n' \ 400 | '- latin2, iso88592 (88592),\n' \ 401 | '- cp1251 (1251),\n' \ 402 | '- cp1252 (1252),\n' \ 403 | '- cp1253 (1253),\n' \ 404 | '- cp1254 (1254),\n' \ 405 | '- cp1250 (1250),' \ 406 | 407 | def options(self): 408 | return [ 409 | fb.FBCommandArgument(arg='encoding', short='-e', long='--encoding', type='string', help='Used encoding (default utf-8).', default='utf-8') 410 | ] 411 | 412 | def args(self): 413 | return [ 414 | fb.FBCommandArgument(arg='data', type='NSData *', help='NSData object.') 415 | ] 416 | 417 | def run(self, arguments, option): 418 | # Normalize encoding. 419 | encoding_text = option.encoding.lower().replace(' -', '') 420 | enc = 4 # Default encoding UTF-8. 421 | if encoding_text == 'ascii': 422 | enc = 1 423 | elif encoding_text == 'utf8': 424 | enc = 4 425 | elif encoding_text == 'latin1' or encoding_text == '88591' or encoding_text == 'iso88591': 426 | enc = 5 427 | elif encoding_text == 'latin2' or encoding_text == '88592' or encoding_text == 'iso88592': 428 | enc = 9 429 | elif encoding_text == 'unicode' or encoding_text == 'utf16': 430 | enc = 10 431 | elif encoding_text == '1251' or encoding_text == 'cp1251': 432 | enc = 11 433 | elif encoding_text == '1252' or encoding_text == 'cp1252': 434 | enc = 12 435 | elif encoding_text == '1253' or encoding_text == 'cp1253': 436 | enc = 13 437 | elif encoding_text == '1254' or encoding_text == 'cp1254': 438 | enc = 14 439 | elif encoding_text == '1250' or encoding_text == 'cp1250': 440 | enc = 15 441 | elif encoding_text == 'utf16b': 442 | enc = 0x90000100 443 | elif encoding_text == 'utf16l': 444 | enc = 0x94000100 445 | elif encoding_text == 'utf32': 446 | enc = 0x8c000100 447 | elif encoding_text == 'utf32b': 448 | enc = 0x98000100 449 | elif encoding_text == 'utf32l': 450 | enc = 0x9c000100 451 | 452 | print (fb.describeObject('[[NSString alloc] initWithData:{} encoding:{}]'.format(arguments[0], enc))) 453 | 454 | class FBPrintTargetActions(fb.FBCommand): 455 | 456 | def name(self): 457 | return 'pactions' 458 | 459 | def description(self): 460 | return 'Print the actions and targets of a control.' 461 | 462 | def args(self): 463 | return [ fb.FBCommandArgument(arg='control', type='UIControl *', help='The control to inspect the actions of.') ] 464 | 465 | def run(self, arguments, options): 466 | control = fb.evaluateInputExpression(arguments[0]) 467 | targets = fb.evaluateObjectExpression('[[{control} allTargets] allObjects]'.format(control=control)) 468 | targetCount = fb.evaluateIntegerExpression('[{targets} count]'.format(targets=targets)) 469 | 470 | for index in range(0, targetCount): 471 | target = fb.evaluateObjectExpression('[{targets} objectAtIndex:{index}]'.format(targets=targets, index=index)) 472 | actions = fb.evaluateObjectExpression('[{control} actionsForTarget:{target} forControlEvent:0]'.format(control=control, target=target)) 473 | 474 | targetDescription = fb.evaluateExpressionValue('(id){target}'.format(target=target)).GetObjectDescription() 475 | actionsDescription = fb.evaluateExpressionValue('(id)[{actions} componentsJoinedByString:@", "]'.format(actions=actions)).GetObjectDescription() 476 | 477 | print ('{target}: {actions}'.format(target=targetDescription, actions=actionsDescription)) 478 | 479 | class FBPrintJSON(fb.FBCommand): 480 | 481 | def name(self): 482 | return 'pjson' 483 | 484 | def description(self): 485 | return 'Print JSON representation of NSDictionary or NSArray object' 486 | 487 | def options(self): 488 | return [ 489 | fb.FBCommandArgument(arg='plain', short='-p', long='--plain', boolean=True, default=False, help='Plain JSON') 490 | ] 491 | 492 | def args(self): 493 | return [ fb.FBCommandArgument(arg='object', type='id', help='The NSDictionary or NSArray object to print') ] 494 | 495 | def run(self, arguments, options): 496 | objectToPrint = fb.evaluateInputExpression(arguments[0]) 497 | pretty = 1 if options.plain is None else 0 498 | jsonData = fb.evaluateObjectExpression('[NSJSONSerialization dataWithJSONObject:(id){} options:{} error:nil]'.format(objectToPrint, pretty)) 499 | jsonString = fb.evaluateExpressionValue('(NSString*)[[NSString alloc] initWithData:(id){} encoding:4]'.format(jsonData)).GetObjectDescription() 500 | 501 | print (jsonString) 502 | 503 | class FBPrintSwiftJSON(fb.FBCommand): 504 | 505 | def name(self): 506 | return 'psjson' 507 | 508 | def description(self): 509 | return 'Print JSON representation of Swift Dictionary or Swift Array object' 510 | 511 | def options(self): 512 | return [ fb.FBCommandArgument(arg='plain', short='-p', long='--plain', boolean=True, default=False, help='Plain JSON') ] 513 | 514 | def args(self): 515 | return [ fb.FBCommandArgument(arg='object', type='NSObject *', help='The Swift Dictionary or Swift Array to print') ] 516 | 517 | def run(self, arguments, options): 518 | #Convert to NSObject first to allow for objc runtime to process it 519 | objectToPrint = fb.evaluateInputExpression('{obj} as NSObject'.format(obj=arguments[0])) 520 | pretty = 1 if options.plain is None else 0 521 | jsonData = fb.evaluateObjectExpression('[NSJSONSerialization dataWithJSONObject:(NSObject*){} options:{} error:nil]'.format(objectToPrint, pretty)) 522 | jsonString = fb.evaluateExpressionValue('(NSString*)[[NSString alloc] initWithData:(NSObject*){} encoding:4]'.format(jsonData)).GetObjectDescription() 523 | 524 | print (jsonString) 525 | 526 | class FBPrintAsCurl(fb.FBCommand): 527 | def name(self): 528 | return 'pcurl' 529 | 530 | def description(self): 531 | return 'Print the NSURLRequest (HTTP) as curl command.' 532 | 533 | def options(self): 534 | return [ 535 | fb.FBCommandArgument(short='-e', long='--embed-data', arg='embed', boolean=True, default=False, help='Embed request data as base64.'), 536 | ] 537 | 538 | def args(self): 539 | return [ fb.FBCommandArgument(arg='request', type='NSURLRequest*/NSMutableURLRequest*', help='The request to convert to the curl command.') ] 540 | 541 | def generateTmpFilePath(self): 542 | return '/tmp/curl_data_{}'.format(fb.evaluateExpression('(NSTimeInterval)[NSDate timeIntervalSinceReferenceDate]')) 543 | 544 | def run(self, arguments, options): 545 | request = fb.evaluateInputExpression(arguments[0]) 546 | HTTPHeaderSring = '' 547 | HTTPMethod = fb.evaluateExpressionValue('(id)[{} HTTPMethod]'.format(request)).GetObjectDescription() 548 | URL = fb.evaluateExpressionValue('(id)[{} URL]'.format(request)).GetObjectDescription() 549 | timeout = fb.evaluateExpression('(NSTimeInterval)[{} timeoutInterval]'.format(request)) 550 | HTTPHeaders = fb.evaluateObjectExpression('(id)[{} allHTTPHeaderFields]'.format(request)) 551 | HTTPHeadersCount = fb.evaluateIntegerExpression('[{} count]'.format(HTTPHeaders)) 552 | allHTTPKeys = fb.evaluateObjectExpression('[{} allKeys]'.format(HTTPHeaders)) 553 | for index in range(0, HTTPHeadersCount): 554 | key = fb.evaluateObjectExpression('[{} objectAtIndex:{}]'.format(allHTTPKeys, index)) 555 | keyDescription = fb.evaluateExpressionValue('(id){}'.format(key)).GetObjectDescription() 556 | value = fb.evaluateExpressionValue('(id)[(id){} objectForKey:{}]'.format(HTTPHeaders, key)).GetObjectDescription() 557 | if len(HTTPHeaderSring) > 0: 558 | HTTPHeaderSring += ' ' 559 | HTTPHeaderSring += '-H "{}: {}"'.format(keyDescription, value) 560 | HTTPData = fb.evaluateObjectExpression('[{} HTTPBody]'.format(request)) 561 | dataFile = None 562 | dataAsString = None 563 | if fb.evaluateIntegerExpression('[{} length]'.format(HTTPData)) > 0: 564 | if options.embed: 565 | if fb.evaluateIntegerExpression('[{} respondsToSelector:@selector(base64EncodedStringWithOptions:)]'.format(HTTPData)): 566 | dataAsString = fb.evaluateExpressionValue('(id)[(id){} base64EncodedStringWithOptions:0]'.format(HTTPData)).GetObjectDescription() 567 | else : 568 | print ('This version of OS doesn\'t supports base64 data encoding') 569 | return False 570 | elif not runtimeHelpers.isIOSDevice(): 571 | dataFile = self.generateTmpFilePath() 572 | if not fb.evaluateBooleanExpression('(BOOL)[{} writeToFile:@"{}" atomically:NO]'.format(HTTPData, dataFile)): 573 | print ('Can\'t write data to file {}'.format(dataFile)) 574 | return False 575 | else: 576 | print ('HTTPBody data for iOS Device is supported only with "--embed-data" flag') 577 | return False 578 | 579 | commandString = '' 580 | if dataAsString is not None and len(dataAsString) > 0: 581 | dataFile = self.generateTmpFilePath() 582 | commandString += 'echo "{}" | base64 -D -o "{}" && '.format(dataAsString, dataFile) 583 | commandString += 'curl -X {} --connect-timeout {}'.format(HTTPMethod, timeout) 584 | if len(HTTPHeaderSring) > 0: 585 | commandString += ' ' + HTTPHeaderSring 586 | if dataFile is not None: 587 | commandString += ' --data-binary @"{}"'.format(dataFile) 588 | 589 | commandString += ' "{}"'.format(URL) 590 | print (commandString) 591 | 592 | class FBPrintToClipboard(fb.FBCommand): 593 | def name(self): 594 | return 'pbcopy' 595 | 596 | def description(self): 597 | return 'Print object and copy output to clipboard' 598 | 599 | def args(self): 600 | return [ fb.FBCommandArgument(arg='object', type='id', help='The object to print') ] 601 | 602 | def run(self, arguments, options): 603 | lldbOutput = fb.evaluateExpressionValue("[{changeset} description]".format(changeset = arguments[0])).GetObjectDescription() 604 | process = subprocess.Popen( 605 | 'pbcopy', env={'LANG': 'en_US.UTF-8'}, stdin=subprocess.PIPE) 606 | process.communicate(lldbOutput.encode('utf-8')) 607 | print ("Object copied to clipboard") 608 | 609 | class FBPrintObjectInObjc(fb.FBCommand): 610 | def name(self): 611 | return 'poobjc' 612 | 613 | def description(self): 614 | return 'Print the expression result, with the expression run in an ObjC++ context. (Shortcut for "expression -O -l ObjC++ -- " )' 615 | 616 | def args(self): 617 | return [ 618 | fb.FBCommandArgument(arg='expression', help='ObjC expression to evaluate and print.'), 619 | ] 620 | 621 | def run(self, arguments, options): 622 | expression = arguments[0] 623 | lldb.debugger.HandleCommand('expression -O -l ObjC++ -- ' + expression) 624 | -------------------------------------------------------------------------------- /libexec/commands/FBTextInputCommands.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Copyright (c) 2016, Facebook, Inc. 4 | # All rights reserved. 5 | # 6 | # This source code is licensed under the BSD-style license found in the 7 | # LICENSE file in the root directory of this source tree. An additional grant 8 | # of patent rights can be found in the PATENTS file in the same directory. 9 | 10 | import os 11 | 12 | import lldb 13 | import fblldbbase as fb 14 | import fblldbviewhelpers as viewHelpers 15 | 16 | ACCESSIBILITY_ID = 0 17 | REPLACEMENT_TEXT = 1 18 | INPUT_TEXT = 0 19 | 20 | 21 | def lldbcommands(): 22 | return [ 23 | FBInputTexByAccessibilityIdCommand(), 24 | FBInputTexToFirstResponderCommand(), 25 | ] 26 | 27 | 28 | class FBInputTexByAccessibilityIdCommand(fb.FBCommand): 29 | def name(self): 30 | return 'settext' 31 | 32 | def description(self): 33 | return 'Set text on text on a view by accessibility id.' 34 | 35 | def args(self): 36 | return [ 37 | fb.FBCommandArgument(arg='accessibilityId', type='string', help='The accessibility ID of the input view.'), 38 | fb.FBCommandArgument(arg='replacementText', type='string', help='The text to set.') 39 | ] 40 | 41 | def run(self, arguments, options): 42 | self.findView(rootView(), arguments[ACCESSIBILITY_ID], arguments[REPLACEMENT_TEXT]) 43 | 44 | def findView(self, view, searchIdentifier, replacementText): 45 | views = subviewsOfView(view) 46 | for index in range(0, viewsCount(views)): 47 | subview = subviewAtIndex(views, index) 48 | self.findView(subview, searchIdentifier, replacementText) 49 | else: 50 | identifier = accessibilityIdentifier(view) 51 | if isEqualToString(identifier, searchIdentifier): 52 | setTextInView(view, replacementText) 53 | 54 | 55 | class FBInputTexToFirstResponderCommand(fb.FBCommand): 56 | def name(self): 57 | return 'setinput' 58 | 59 | def description(self): 60 | return 'Input text into text field or text view that is first responder.' 61 | 62 | def args(self): 63 | return [ 64 | fb.FBCommandArgument(arg='inputText', type='string', help='The text to input.') 65 | ] 66 | 67 | def run(self, arguments, options): 68 | self.findFirstResponder(rootView(), arguments[INPUT_TEXT]) 69 | 70 | def findFirstResponder(self, view, replacementText): 71 | views = subviewsOfView(view) 72 | if isFirstResponder(view): 73 | setTextInView(view, replacementText) 74 | else: 75 | for index in range(0, viewsCount(views)): 76 | subview = subviewAtIndex(views, index) 77 | self.findFirstResponder(subview, replacementText) 78 | 79 | 80 | # Some helpers 81 | def rootView(): 82 | return fb.evaluateObjectExpression('[[UIApplication sharedApplication] keyWindow]') 83 | 84 | def subviewsOfView(view): 85 | return fb.evaluateObjectExpression('[%s subviews]' % view) 86 | 87 | def subviewAtIndex(views, index): 88 | return fb.evaluateObjectExpression('[%s objectAtIndex:%i]' % (views, index)) 89 | 90 | def viewsCount(views): 91 | return int(fb.evaluateExpression('(int)[%s count]' % views)) 92 | 93 | def accessibilityIdentifier(view): 94 | return fb.evaluateObjectExpression('[%s accessibilityIdentifier]' % view) 95 | 96 | def isEqualToString(identifier, needle): 97 | return fb.evaluateBooleanExpression('[%s isEqualToString:@"%s"]' % (identifier, needle)) 98 | 99 | def setTextInView(view, text): 100 | fb.evaluateObjectExpression('[%s setText:@"%s"]' % (view, text)) 101 | viewHelpers.flushCoreAnimationTransaction() 102 | 103 | def isFirstResponder(view): 104 | return fb.evaluateBooleanExpression('[%s isFirstResponder]' % view) 105 | -------------------------------------------------------------------------------- /libexec/commands/FBVisualizationCommands.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Copyright (c) 2014, Facebook, Inc. 4 | # All rights reserved. 5 | # 6 | # This source code is licensed under the BSD-style license found in the 7 | # LICENSE file in the root directory of this source tree. An additional grant 8 | # of patent rights can be found in the PATENTS file in the same directory. 9 | 10 | import os 11 | import time 12 | 13 | import lldb 14 | import errno 15 | import fblldbbase as fb 16 | import fblldbobjecthelpers as objectHelpers 17 | 18 | def lldbcommands(): 19 | return [ 20 | FBVisualizeCommand() 21 | ] 22 | 23 | def _showImage(commandForImage): 24 | imageDirectory = '/tmp/xcode_debug_images/' 25 | 26 | imageName = time.strftime("%Y-%m-%d-%H-%M-%S", time.gmtime()) + ".png" 27 | imagePath = imageDirectory + imageName 28 | 29 | try: 30 | os.makedirs(imageDirectory) 31 | except OSError as e: 32 | if e.errno == errno.EEXIST and os.path.isdir(imageDirectory): 33 | pass 34 | else: 35 | raise 36 | 37 | toPNG = '(id)UIImagePNGRepresentation((id){})'.format(commandForImage) 38 | imageDataAddress = fb.evaluateExpressionValue(toPNG, tryAllThreads=True).GetValue() 39 | imageBytesStartAddress = fb.evaluateExpression('(void *)[(id)' + imageDataAddress + ' bytes]') 40 | imageBytesLength = fb.evaluateExpression('(NSUInteger)[(id)' + imageDataAddress + ' length]') 41 | 42 | address = int(imageBytesStartAddress, 16) 43 | length = int(imageBytesLength) 44 | 45 | if not (address or length): 46 | print ('Could not get image data.') 47 | return 48 | 49 | process = lldb.debugger.GetSelectedTarget().GetProcess() 50 | error = lldb.SBError() 51 | mem = process.ReadMemory(address, length, error) 52 | 53 | if error is not None and str(error) != 'success': 54 | print (error) 55 | else: 56 | imgFile = open(imagePath, 'wb') 57 | imgFile.write(mem) 58 | imgFile.close() 59 | os.system('open ' + imagePath) 60 | 61 | def _colorIsCGColorRef(color): 62 | color = '(CGColorRef)(' + color + ')' 63 | 64 | result = fb.evaluateExpressionValue('(unsigned long)CFGetTypeID({color}) == (unsigned long)CGColorGetTypeID()'.format(color=color)) 65 | 66 | if result.GetError() is not None and str(result.GetError()) != 'success': 67 | print ("got error: {}".format(result)) 68 | return False 69 | else: 70 | isCFColor = result.GetValueAsUnsigned() != 0 71 | return isCFColor 72 | 73 | def _showColor(color): 74 | color = '(' + color + ')' 75 | 76 | colorToUse = color 77 | isCF = _colorIsCGColorRef(color) 78 | if isCF: 79 | colorToUse = '[[UIColor alloc] initWithCGColor:(CGColorRef){}]'.format(color) 80 | else: 81 | isCI = objectHelpers.isKindOfClass(color, 'CIColor') 82 | if isCI: 83 | colorToUse = '[UIColor colorWithCIColor:(CIColor *){}]'.format(color) 84 | 85 | imageSize = 58 86 | fb.evaluateEffect('UIGraphicsBeginImageContextWithOptions((CGSize)CGSizeMake({imageSize}, {imageSize}), NO, 0.0)'.format(imageSize=imageSize)) 87 | fb.evaluateEffect('[(id){} setFill]'.format(colorToUse)) 88 | fb.evaluateEffect('UIRectFill((CGRect)CGRectMake(0.0, 0.0, {imageSize}, {imageSize}))'.format(imageSize=imageSize)) 89 | 90 | result = fb.evaluateExpressionValue('(UIImage *)UIGraphicsGetImageFromCurrentImageContext()') 91 | if result.GetError() is not None and str(result.GetError()) != 'success': 92 | print ("got error {}".format(result)) 93 | print (result.GetError()) 94 | else: 95 | image = result.GetValue() 96 | _showImage(image) 97 | 98 | fb.evaluateEffect('UIGraphicsEndImageContext()') 99 | 100 | def _showLayer(layer): 101 | layer = '(' + layer + ')' 102 | size = '((CGRect)[(id)' + layer + ' bounds]).size' 103 | 104 | width = float(fb.evaluateExpression('(CGFloat)(' + size + '.width)')) 105 | height = float(fb.evaluateExpression('(CGFloat)(' + size + '.height)')) 106 | if width == 0.0 or height == 0.0: 107 | print ('Nothing to see here - the size of this element is {} x {}.'.format(width, height)) 108 | return 109 | 110 | fb.evaluateEffect('UIGraphicsBeginImageContextWithOptions(' + size + ', NO, 0.0)') 111 | fb.evaluateEffect('[(id)' + layer + ' renderInContext:(void *)UIGraphicsGetCurrentContext()]') 112 | 113 | result = fb.evaluateExpressionValue('(UIImage *)UIGraphicsGetImageFromCurrentImageContext()') 114 | if result.GetError() is not None and str(result.GetError()) != 'success': 115 | print (result.GetError()) 116 | else: 117 | image = result.GetValue() 118 | _showImage(image) 119 | 120 | fb.evaluateEffect('UIGraphicsEndImageContext()') 121 | 122 | def _dataIsImage(data): 123 | data = '(' + data + ')' 124 | 125 | result = fb.evaluateExpressionValue('(id)[UIImage imageWithData:' + data + ']') 126 | 127 | if result.GetError() is not None and str(result.GetError()) != 'success': 128 | return False 129 | else: 130 | isImage = result.GetValueAsUnsigned() != 0 131 | return isImage 132 | 133 | def _dataIsString(data): 134 | data = '(' + data + ')' 135 | 136 | result = fb.evaluateExpressionValue('(NSString*)[[NSString alloc] initWithData:' + data + ' encoding:4]') 137 | 138 | if result.GetError() is not None and str(result.GetError()) != 'success': 139 | return False 140 | else: 141 | isString = result.GetValueAsUnsigned() != 0 142 | return isString 143 | 144 | def _visualize(target): 145 | target = fb.evaluateInputExpression(target) 146 | 147 | if fb.evaluateBooleanExpression('(unsigned long)CFGetTypeID((CFTypeRef)' + target + ') == (unsigned long)CGImageGetTypeID()'): 148 | _showImage('(id)[UIImage imageWithCGImage:' + target + ']') 149 | else: 150 | if objectHelpers.isKindOfClass(target, 'UIImage'): 151 | _showImage(target) 152 | elif objectHelpers.isKindOfClass(target, 'UIView'): 153 | _showLayer('[(id)' + target + ' layer]') 154 | elif objectHelpers.isKindOfClass(target, 'CALayer'): 155 | _showLayer(target) 156 | elif objectHelpers.isKindOfClass(target, 'UIColor') or objectHelpers.isKindOfClass(target, 'CIColor') or _colorIsCGColorRef(target): 157 | _showColor(target) 158 | elif objectHelpers.isKindOfClass(target, 'NSData'): 159 | if _dataIsImage(target): 160 | _showImage('(id)[UIImage imageWithData:' + target + ']') 161 | elif _dataIsString(target): 162 | print (fb.describeObject('[[NSString alloc] initWithData:' + target + ' encoding:4]')) 163 | else: 164 | print ('Data isn\'t an image and isn\'t a string.') 165 | else: 166 | print ('{} isn\'t supported. You can visualize UIImage, CGImageRef, UIView, CALayer, NSData, UIColor, CIColor, or CGColorRef.'.format(objectHelpers.className(target))) 167 | 168 | class FBVisualizeCommand(fb.FBCommand): 169 | def name(self): 170 | return 'visualize' 171 | 172 | def description(self): 173 | return 'Open a UIImage, CGImageRef, UIView, or CALayer in Preview.app on your Mac.' 174 | 175 | def args(self): 176 | return [ fb.FBCommandArgument(arg='target', type='(id)', help='The object to visualize.') ] 177 | 178 | def run(self, arguments, options): 179 | _visualize(arguments[0]) 180 | -------------------------------------------------------------------------------- /libexec/commands/FBXCTestCommands.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Copyright (c) 2017, Facebook, Inc. 4 | # All rights reserved. 5 | # 6 | # This source code is licensed under the BSD-style license found in the 7 | # LICENSE file in the root directory of this source tree. An additional grant 8 | # of patent rights can be found in the PATENTS file in the same directory. 9 | 10 | import lldb 11 | import fblldbbase as fb 12 | import re 13 | 14 | NOT_FOUND = 0xffffffff # UINT32_MAX 15 | 16 | 17 | def lldbcommands(): 18 | return [ 19 | FBXCPrintDebugDescription(), 20 | FBXCPrintTree(), 21 | FBXCPrintObject(), 22 | FBXCNoId(), 23 | ] 24 | 25 | 26 | class FBXCPrintDebugDescription(fb.FBCommand): 27 | def name(self): 28 | return 'xdebug' 29 | 30 | def description(self): 31 | return 'Print debug description the XCUIElement in human readable format.' 32 | 33 | def args(self): 34 | return [fb.FBCommandArgument(arg='element', type='XCUIElement*', help='The element to print debug description.', default='__default__')] 35 | 36 | def run(self, arguments, options): 37 | element = arguments[0] 38 | language = fb.currentLanguage() 39 | 40 | if element == '__default__': 41 | element = 'XCUIApplication()' if language == lldb.eLanguageTypeSwift else '(XCUIApplication *)[[XCUIApplication alloc] init]' 42 | 43 | if language == lldb.eLanguageTypeSwift: 44 | print (fb.evaluateExpressionValue("{}.debugDescription".format(element), language=language) \ 45 | .GetObjectDescription() \ 46 | .replace("\\n", "\n") \ 47 | .replace("\\'", "'") \ 48 | .strip(' "\n\t')) 49 | else: 50 | print (fb.evaluateExpressionValue("[{} debugDescription]".format(element)).GetObjectDescription()) 51 | 52 | 53 | class FBXCPrintTree(fb.FBCommand): 54 | def name(self): 55 | return "xtree" 56 | 57 | def description(self): 58 | return "Print XCUIElement subtree." 59 | 60 | def args(self): 61 | return [fb.FBCommandArgument(arg="element", type="XCUIElement*", help="The element to print tree.", default="__default__")] 62 | 63 | def options(self): 64 | return [ 65 | fb.FBCommandArgument(arg='pointer', short='-p', long='--pointer', type='BOOL', boolean=True, default=False, help='Print pointers'), 66 | fb.FBCommandArgument(arg='trait', short='-t', long='--traits', type='BOOL', boolean=True, default=False, help='Print traits'), 67 | fb.FBCommandArgument(arg='frame', short='-f', long='--frame', type='BOOL', boolean=True, default=False, help='Print frames') 68 | ] 69 | 70 | def run(self, arguments, options): 71 | element = arguments[0] 72 | language = fb.currentLanguage() 73 | if element == "__default__": 74 | element = "XCUIApplication()" if language == lldb.eLanguageTypeSwift else "(XCUIApplication *)[[XCUIApplication alloc] init]" 75 | 76 | # Evaluate object 77 | element_sbvalue = fb.evaluateExpressionValue("{}".format(element), language=language) 78 | """:type: lldb.SBValue""" 79 | 80 | # Get pointer value, so it will be working in Swift and Objective-C 81 | element_pointer = int(element_sbvalue.GetValue(), 16) 82 | 83 | # Get XCElementSnapshot object 84 | snapshot = take_snapshot(element_pointer) 85 | 86 | # Print tree for snapshot element 87 | snapshot_object = XCElementSnapshot(snapshot, language=language) 88 | print (snapshot_object.tree().hierarchy_text(pointer=options.pointer, trait=options.trait, frame=options.frame)) 89 | 90 | 91 | class FBXCPrintObject(fb.FBCommand): 92 | def name(self): 93 | return "xobject" 94 | 95 | def description(self): 96 | return "Print XCUIElement details." 97 | 98 | def args(self): 99 | return [fb.FBCommandArgument(arg="element", type="XCUIElement*", help="The element to print details.", default="__default__")] 100 | 101 | def run(self, arguments, options): 102 | element = arguments[0] 103 | language = fb.currentLanguage() 104 | if element == "__default__": 105 | element = "XCUIApplication()" if language == lldb.eLanguageTypeSwift else "(XCUIApplication *)[[XCUIApplication alloc] init]" 106 | 107 | # Evaluate object 108 | element_sbvalue = fb.evaluateExpressionValue("{}".format(element), language=language) 109 | """:type: lldb.SBValue""" 110 | 111 | # Get pointer value, so it will be working in Swift and Objective-C 112 | element_pointer = int(element_sbvalue.GetValue(), 16) 113 | 114 | # Get XCElementSnapshot object 115 | snapshot = take_snapshot(element_pointer) 116 | 117 | # Print details of snapshot element 118 | snapshot_object = XCElementSnapshot(snapshot, language=language) 119 | print (snapshot_object.detail_summary()) 120 | 121 | 122 | class FBXCNoId(fb.FBCommand): 123 | def name(self): 124 | return "xnoid" 125 | 126 | def description(self): 127 | return "Print XCUIElement objects with label but without identifier." 128 | 129 | def args(self): 130 | return [fb.FBCommandArgument(arg="element", type="XCUIElement*", help="The element from start to.", default="__default__")] 131 | 132 | def options(self): 133 | return [ 134 | fb.FBCommandArgument(arg='status_bar', short='-s', long='--status-bar', type='BOOL', boolean=True, default=False, help='Print status bar items'), 135 | fb.FBCommandArgument(arg='pointer', short='-p', long='--pointer', type='BOOL', boolean=True, default=False, help='Print pointers'), 136 | fb.FBCommandArgument(arg='trait', short='-t', long='--traits', type='BOOL', boolean=True, default=False, help='Print traits'), 137 | fb.FBCommandArgument(arg='frame', short='-f', long='--frame', type='BOOL', boolean=True, default=False, help='Print frames') 138 | ] 139 | 140 | def run(self, arguments, options): 141 | element = arguments[0] 142 | language = fb.currentLanguage() 143 | if element == "__default__": 144 | element = "XCUIApplication()" if language == lldb.eLanguageTypeSwift else "(XCUIApplication *)[[XCUIApplication alloc] init]" 145 | 146 | # Evaluate object 147 | element_sbvalue = fb.evaluateExpressionValue("{}".format(element), language=language) 148 | """:type: lldb.SBValue""" 149 | 150 | # Get pointer value, so it will be working in Swift and Objective-C 151 | element_pointer = int(element_sbvalue.GetValue(), 16) 152 | 153 | # Get XCElementSnapshot object 154 | snapshot = take_snapshot(element_pointer) 155 | 156 | # Print tree for snapshot element 157 | snapshot_object = XCElementSnapshot(snapshot, language=language) 158 | elements = snapshot_object.find_missing_identifiers(status_bar=options.status_bar) 159 | if elements is not None: 160 | print (elements.hierarchy_text(pointer=options.pointer, trait=options.trait, frame=options.frame)) 161 | else: 162 | print ("Couldn't found elements without identifier") 163 | 164 | 165 | def take_snapshot(element): 166 | """ 167 | Takes snapshot (XCElementSnapshot) from XCUIElement (as pointer) 168 | 169 | :param int element: Pointer to the XCUIElement 170 | :return: XCElementSnapshot object 171 | :rtype: lldb.SBValue 172 | """ 173 | return fb.evaluateExpressionValue("(XCElementSnapshot *)[[[{} query] matchingSnapshotsWithError:nil] firstObject]".format(element)) 174 | 175 | 176 | class _ElementList(object): 177 | """ 178 | Store element and list of children 179 | 180 | :param XCElementSnapshot element: XCElementSnapshot 181 | :param list[_ElementList] children: List of XCElementSnapshot objects 182 | """ 183 | def __init__(self, element, children): 184 | self.element = element 185 | self.children = children 186 | 187 | def text(self, pointer, trait, frame, indent): 188 | """ 189 | String representation of the element 190 | 191 | :param bool pointer: Print pointers 192 | :param bool trait: Print traits 193 | :param bool frame: Print frames 194 | :param int indent: Indention 195 | :return: String representation of the element 196 | :rtype: str 197 | """ 198 | indent_string = ' | ' * indent 199 | return "{}{}\n".format(indent_string, self.element.summary(pointer=pointer, trait=trait, frame=frame)) 200 | 201 | def hierarchy_text(self, pointer=False, trait=False, frame=False, indent=0): 202 | """ 203 | String representation of the hierarchy of elements 204 | 205 | :param bool pointer: Print pointers 206 | :param bool trait: Print traits 207 | :param bool frame: Print frames 208 | :param int indent: Indention 209 | :return: String representation of the hierarchy of elements 210 | :rtype: str 211 | """ 212 | s = self.text(pointer=pointer, trait=trait, frame=frame, indent=indent) 213 | for e in self.children: 214 | s += e.hierarchy_text(pointer=pointer, trait=trait, frame=frame, indent=indent+1) 215 | return s 216 | 217 | 218 | class XCElementSnapshot(object): 219 | """ 220 | XCElementSnapshot wrapper 221 | 222 | :param lldb.SBValue element: XCElementSnapshot object 223 | :param str element_value: Pointer to XCElementSnapshot object 224 | :param language: Project language 225 | :param lldb.SBValue _type: XCUIElement type / XCUIElementType 226 | :param lldb.SBValue _traits: UIAccessibilityTraits 227 | :param lldb.SBValue | None _frame: XCUIElement frame 228 | :param lldb.SBValue _identifier: XCUIElement identifier 229 | :param lldb.SBValue _value: XCUIElement value 230 | :param lldb.SBValue _placeholderValue: XCUIElement placeholder value 231 | :param lldb.SBValue _label: XCUIElement label 232 | :param lldb.SBValue _title: XCUIElement title 233 | :param lldb.SBValue _children: XCUIElement children 234 | :param lldb.SBValue _enabled: XCUIElement is enabled 235 | :param lldb.SBValue _selected: XCUIElement is selected 236 | :param lldb.SBValue _isMainWindow: XCUIElement is main window 237 | :param lldb.SBValue _hasKeyboardFocus: XCUIElement has keyboard focus 238 | :param lldb.SBValue _hasFocus: XCUIElement has focus 239 | :param lldb.SBValue _generation: XCUIElement generation 240 | :param lldb.SBValue _horizontalSizeClass: XCUIElement horizontal class 241 | :param lldb.SBValue _verticalSizeClass: XCUIElement vertical class 242 | """ 243 | def __init__(self, element, language): 244 | """ 245 | :param lldb.SBValue element: XCElementSnapshot object 246 | :param language: Project language 247 | """ 248 | super(XCElementSnapshot, self).__init__() 249 | self.element = element 250 | self.element_value = self.element.GetValue() 251 | self.language = language 252 | 253 | self._type = None 254 | self._traits = None 255 | self._frame = None 256 | self._identifier = None 257 | self._value = None 258 | self._placeholderValue = None 259 | self._label = None 260 | self._title = None 261 | self._children = None 262 | 263 | self._enabled = None 264 | self._selected = None 265 | self._isMainWindow = None 266 | self._hasKeyboardFocus = None 267 | self._hasFocus = None 268 | self._generation = None 269 | self._horizontalSizeClass = None 270 | self._verticalSizeClass = None 271 | 272 | @property 273 | def is_missing_identifier(self): 274 | """ 275 | Checks if element has a label but doesn't have an identifier. 276 | 277 | :return: True if element has a label but doesn't have an identifier. 278 | :rtype: bool 279 | """ 280 | return len(self.identifier_value) == 0 and len(self.label_value) > 0 281 | 282 | @property 283 | def type(self): 284 | """ 285 | :return: XCUIElement type / XCUIElementType 286 | :rtype: lldb.SBValue 287 | """ 288 | if self._type is None: 289 | name = "_elementType" 290 | if self.element.GetIndexOfChildWithName(name) == NOT_FOUND: 291 | self._type = fb.evaluateExpressionValue("(int)[{} elementType]".format(self.element_value)) 292 | else: 293 | self._type = self.element.GetChildMemberWithName(name) 294 | return self._type 295 | 296 | @property 297 | def type_value(self): 298 | """ 299 | :return: XCUIElementType value 300 | :rtype: int 301 | """ 302 | return int(self.type.GetValue()) 303 | 304 | @property 305 | def type_summary(self): 306 | """ 307 | :return: XCUIElementType summary 308 | :rtype: str 309 | """ 310 | return self.get_type_value_string(self.type_value) 311 | 312 | @property 313 | def traits(self): 314 | """ 315 | :return: UIAccessibilityTraits 316 | :rtype: lldb.SBValue 317 | """ 318 | if self._traits is None: 319 | name = "_traits" 320 | if self.element.GetIndexOfChildWithName(name) == NOT_FOUND: 321 | self._traits = fb.evaluateExpressionValue("(int)[{} traits]".format(self.element_value)) 322 | else: 323 | self._traits = self.element.GetChildMemberWithName(name) 324 | return self._traits 325 | 326 | @property 327 | def traits_value(self): 328 | """ 329 | :return: UIAccessibilityTraits value 330 | :rtype: int 331 | """ 332 | return int(self.traits.GetValue()) 333 | 334 | @property 335 | def traits_summary(self): 336 | """ 337 | :return: UIAccessibilityTraits summary 338 | :rtype: str 339 | """ 340 | return self.get_traits_value_string(self.traits_value) 341 | 342 | @property 343 | def frame(self): 344 | """ 345 | :return: XCUIElement frame 346 | :rtype: lldb.SBValue 347 | """ 348 | if self._frame is None: 349 | import_uikit() 350 | name = "_frame" 351 | if self.element.GetIndexOfChildWithName(name) == NOT_FOUND: 352 | self._frame = fb.evaluateExpressionValue("(CGRect)[{} frame]".format(self.element_value)) 353 | else: 354 | self._frame = self.element.GetChildMemberWithName(name) 355 | return self._frame 356 | 357 | @property 358 | def frame_summary(self): 359 | """ 360 | :return: XCUIElement frame summary 361 | :rtype: str 362 | """ 363 | return CGRect(self.frame).summary() 364 | 365 | @property 366 | def identifier(self): 367 | """ 368 | :return: XCUIElement identifier 369 | :rtype: lldb.SBValue 370 | """ 371 | if self._identifier is None: 372 | name = "_identifier" 373 | if self.element.GetIndexOfChildWithName(name) == NOT_FOUND: 374 | self._identifier = fb.evaluateExpressionValue("(NSString *)[{} identifier]".format(self.element_value)) 375 | else: 376 | self._identifier = self.element.GetChildMemberWithName(name) 377 | return self._identifier 378 | 379 | @property 380 | def identifier_value(self): 381 | """ 382 | :return: XCUIElement identifier value 383 | :rtype: str 384 | """ 385 | return normalize_summary(self.identifier.GetSummary()) 386 | 387 | @property 388 | def identifier_summary(self): 389 | """ 390 | :return: XCUIElement identifier summary 391 | :rtype: str | None 392 | """ 393 | if len(self.identifier_value) == 0: 394 | return None 395 | return "identifier: '{}'".format(self.identifier_value) 396 | 397 | @property 398 | def value(self): 399 | """ 400 | :return: XCUIElement value 401 | :rtype: lldb.SBValue 402 | """ 403 | if self._value is None: 404 | name = "_value" 405 | if self.element.GetIndexOfChildWithName(name) == NOT_FOUND: 406 | self._value = fb.evaluateExpressionValue("(NSString *)[{} value]".format(self.element_value)) 407 | else: 408 | self._value = self.element.GetChildMemberWithName(name) 409 | return self._value 410 | 411 | @property 412 | def value_value(self): 413 | """ 414 | :return: XCUIElement value value 415 | :rtype: str 416 | """ 417 | return normalize_summary(self.value.GetSummary()) 418 | 419 | @property 420 | def value_summary(self): 421 | """ 422 | :return: XCUIElement value summary 423 | :rtype: str | None 424 | """ 425 | if len(self.value_value) == 0: 426 | return None 427 | return "value: '{}'".format(self.value_value) 428 | 429 | @property 430 | def placeholder(self): 431 | """ 432 | :return: XCUIElement placeholder value 433 | :rtype: lldb.SBValue 434 | """ 435 | if self._placeholderValue is None: 436 | name = "_placeholderValue" 437 | if self.element.GetIndexOfChildWithName(name) == NOT_FOUND: 438 | self._placeholderValue = fb.evaluateExpressionValue("(NSString *)[{} placeholderValue]".format(self.element_value)) 439 | else: 440 | self._placeholderValue = self.element.GetChildMemberWithName(name) 441 | return self._placeholderValue 442 | 443 | @property 444 | def placeholder_value(self): 445 | """ 446 | :return: XCUIElement placeholderValue value 447 | :rtype: str 448 | """ 449 | return normalize_summary(self.placeholder.GetSummary()) 450 | 451 | @property 452 | def placeholder_summary(self): 453 | """ 454 | :return: XCUIElement placeholderValue summary 455 | :rtype: str | None 456 | """ 457 | if len(self.placeholder_value) == 0: 458 | return None 459 | return "placeholderValue: '{}'".format(self.placeholder_value) 460 | 461 | @property 462 | def label(self): 463 | """ 464 | :return: XCUIElement label 465 | :rtype: lldb.SBValue 466 | """ 467 | if self._label is None: 468 | name = "_label" 469 | if self.element.GetIndexOfChildWithName(name) == NOT_FOUND: 470 | self._label = fb.evaluateExpressionValue("(NSString *)[{} label]".format(self.element_value)) 471 | else: 472 | self._label = self.element.GetChildMemberWithName(name) 473 | return self._label 474 | 475 | @property 476 | def label_value(self): 477 | """ 478 | :return: XCUIElement label value 479 | :rtype: str 480 | """ 481 | return normalize_summary(self.label.GetSummary()) 482 | 483 | @property 484 | def label_summary(self): 485 | """ 486 | :return: XCUIElement label summary 487 | :rtype: str | None 488 | """ 489 | if len(self.label_value) == 0: 490 | return None 491 | return "label: '{}'".format(self.label_value) 492 | 493 | @property 494 | def title(self): 495 | """ 496 | :return: XCUIElement title 497 | :rtype: lldb.SBValue 498 | """ 499 | if self._title is None: 500 | name = "_title" 501 | if self.element.GetIndexOfChildWithName(name) == NOT_FOUND: 502 | self._title = fb.evaluateExpressionValue("(NSString *)[{} title]".format(self.element_value)) 503 | else: 504 | self._title = self.element.GetChildMemberWithName(name) 505 | return self._title 506 | 507 | @property 508 | def title_value(self): 509 | """ 510 | :return: XCUIElement title value 511 | :rtype: str 512 | """ 513 | return normalize_summary(self.title.GetSummary()) 514 | 515 | @property 516 | def title_summary(self): 517 | """ 518 | :return: XCUIElement title summary 519 | :rtype: str | None 520 | """ 521 | if len(self.title_value) == 0: 522 | return None 523 | return "title: '{}'".format(self.title_value) 524 | 525 | @property 526 | def children(self): 527 | """ 528 | :return: XCUIElement children 529 | :rtype: lldb.SBValue 530 | """ 531 | if self._children is None: 532 | name = "_children" 533 | if self.element.GetIndexOfChildWithName(name) == NOT_FOUND: 534 | self._children = fb.evaluateExpressionValue("(NSArray *)[{} children]".format(self.element_value)) 535 | else: 536 | self._children = self.element.GetChildMemberWithName(name) 537 | return self._children 538 | 539 | @property 540 | def children_count(self): 541 | """ 542 | :return: XCUIElement children count 543 | :rtype: int 544 | """ 545 | return self.children.GetNumChildren() 546 | 547 | @property 548 | def children_list(self): 549 | """ 550 | :return: XCUIElement children list 551 | :rtype: list[lldb.SBValue] 552 | """ 553 | return [self.children.GetChildAtIndex(i) for i in xrange(0, self.children_count)] 554 | 555 | @property 556 | def enabled(self): 557 | """ 558 | :return: XCUIElement is enabled 559 | :rtype: lldb.SBValue 560 | """ 561 | if self._enabled is None: 562 | name = "_enabled" 563 | if self.element.GetIndexOfChildWithName(name) == NOT_FOUND: 564 | self._enabled = fb.evaluateExpressionValue("(BOOL)[{} enabled]".format(self.element_value)) 565 | else: 566 | self._enabled = self.element.GetChildMemberWithName(name) 567 | return self._enabled 568 | 569 | @property 570 | def enabled_value(self): 571 | """ 572 | :return: XCUIElement is enabled value 573 | :rtype: bool 574 | """ 575 | return bool(self.enabled.GetValueAsSigned()) 576 | 577 | @property 578 | def enabled_summary(self): 579 | """ 580 | :return: XCUIElement is enabled summary 581 | :rtype: str | None 582 | """ 583 | if not self.enabled_value: 584 | return "enabled: {}".format(self.enabled_value) 585 | return None 586 | 587 | @property 588 | def selected(self): 589 | """ 590 | :return: XCUIElement is selected 591 | :rtype: lldb.SBValue 592 | """ 593 | if self._selected is None: 594 | name = "_selected" 595 | if self.element.GetIndexOfChildWithName(name) == NOT_FOUND: 596 | self._selected = fb.evaluateExpressionValue("(BOOL)[{} selected]".format(self.element_value)) 597 | else: 598 | self._selected = self.element.GetChildMemberWithName(name) 599 | return self._selected 600 | 601 | @property 602 | def selected_value(self): 603 | """ 604 | :return: XCUIElement is selected value 605 | :rtype: bool 606 | """ 607 | return bool(self.selected.GetValueAsSigned()) 608 | 609 | @property 610 | def selected_summary(self): 611 | """ 612 | :return: XCUIElement is selected summary 613 | :rtype: str | None 614 | """ 615 | if self.selected_value: 616 | return "selected: {}".format(self.selected_value) 617 | return None 618 | 619 | @property 620 | def is_main_window(self): 621 | """ 622 | :return: XCUIElement isMainWindow 623 | :rtype: lldb.SBValue 624 | """ 625 | if self._isMainWindow is None: 626 | name = "_isMainWindow" 627 | if self.element.GetIndexOfChildWithName(name) == NOT_FOUND: 628 | self._isMainWindow = fb.evaluateExpressionValue("(BOOL)[{} isMainWindow]".format(self.element_value)) 629 | else: 630 | self._isMainWindow = self.element.GetChildMemberWithName(name) 631 | return self._isMainWindow 632 | 633 | @property 634 | def is_main_window_value(self): 635 | """ 636 | :return: XCUIElement isMainWindow value 637 | :rtype: bool 638 | """ 639 | return bool(self.is_main_window.GetValueAsSigned()) 640 | 641 | @property 642 | def is_main_window_summary(self): 643 | """ 644 | :return: XCUIElement isMainWindow summary 645 | :rtype: str | None 646 | """ 647 | if self.is_main_window_value: 648 | return "MainWindow" 649 | return None 650 | 651 | @property 652 | def keyboard_focus(self): 653 | """ 654 | :return: XCUIElement hasKeyboardFocus 655 | :rtype: lldb.SBValue 656 | """ 657 | if self._hasKeyboardFocus is None: 658 | name = "_hasKeyboardFocus" 659 | if self.element.GetIndexOfChildWithName(name) == NOT_FOUND: 660 | self._hasKeyboardFocus = fb.evaluateExpressionValue("(BOOL)[{} hasKeyboardFocus]".format(self.element_value)) 661 | else: 662 | self._hasKeyboardFocus = self.element.GetChildMemberWithName(name) 663 | return self._hasKeyboardFocus 664 | 665 | @property 666 | def keyboard_focus_value(self): 667 | """ 668 | :return: XCUIElement hasKeyboardFocus value 669 | :rtype: bool 670 | """ 671 | return bool(self.keyboard_focus.GetValueAsSigned()) 672 | 673 | @property 674 | def keyboard_focus_summary(self): 675 | """ 676 | :return: XCUIElement hasKeyboardFocus summary 677 | :rtype: str | None 678 | """ 679 | if self.keyboard_focus_value: 680 | return "hasKeyboardFocus: {}".format(self.keyboard_focus_value) 681 | return None 682 | 683 | @property 684 | def focus(self): 685 | """ 686 | :return: XCUIElement hasFocus 687 | :rtype: lldb.SBValue 688 | """ 689 | if self._hasFocus is None: 690 | name = "_hasFocus" 691 | if self.element.GetIndexOfChildWithName(name) == NOT_FOUND: 692 | self._hasFocus = fb.evaluateExpressionValue("(BOOL)[{} hasFocus]".format(self.element_value)) 693 | else: 694 | self._hasFocus = self.element.GetChildMemberWithName(name) 695 | return self._hasFocus 696 | 697 | @property 698 | def focus_value(self): 699 | """ 700 | :return: XCUIElement hasFocus value 701 | :rtype: bool 702 | """ 703 | return bool(self.focus.GetValueAsSigned()) 704 | 705 | @property 706 | def focus_summary(self): 707 | """ 708 | :return: XCUIElement hasFocus summary 709 | :rtype: str | None 710 | """ 711 | if self.focus_value: 712 | return "hasFocus: {}".format(self.focus_value) 713 | return None 714 | 715 | @property 716 | def generation(self): 717 | """ 718 | :return: XCUIElement generation 719 | :rtype: lldb.SBValue 720 | """ 721 | if self._generation is None: 722 | name = "_generation" 723 | if self.element.GetIndexOfChildWithName(name) == NOT_FOUND: 724 | self._generation = fb.evaluateExpressionValue("(unsigned int)[{} generation]".format(self.element_value)) 725 | else: 726 | self._generation = self.element.GetChildMemberWithName(name) 727 | return self._generation 728 | 729 | @property 730 | def generation_value(self): 731 | """ 732 | :return: XCUIElement generation value 733 | :rtype: int 734 | """ 735 | return int(self.generation.GetValueAsUnsigned()) 736 | 737 | @property 738 | def horizontal_size_class(self): 739 | """ 740 | :return: XCUIElement horizontal size class 741 | :rtype: lldb.SBValue 742 | """ 743 | if self._horizontalSizeClass is None: 744 | name = "_horizontalSizeClass" 745 | if self.element.GetIndexOfChildWithName(name) == NOT_FOUND: 746 | self._horizontalSizeClass = fb.evaluateExpressionValue("(int)[{} horizontalSizeClass]".format(self.element_value)) 747 | else: 748 | self._horizontalSizeClass = self.element.GetChildMemberWithName(name) 749 | return self._horizontalSizeClass 750 | 751 | @property 752 | def horizontal_size_class_value(self): 753 | """ 754 | :return: XCUIElement horizontal size class value 755 | :rtype: int 756 | """ 757 | return int(self.horizontal_size_class.GetValue()) 758 | 759 | @property 760 | def horizontal_size_class_summary(self): 761 | """ 762 | :return: XCUIElement horizontal size class summary 763 | """ 764 | return self.get_user_interface_size_class_string(self.horizontal_size_class_value) 765 | 766 | @property 767 | def vertical_size_class(self): 768 | """ 769 | :return: XCUIElement vertical size class 770 | :rtype: lldb.SBValue 771 | """ 772 | if self._verticalSizeClass is None: 773 | name = "_verticalSizeClass" 774 | if self.element.GetIndexOfChildWithName(name) == NOT_FOUND: 775 | self._verticalSizeClass = fb.evaluateExpressionValue("(int)[{} verticalSizeClass]".format(self.element_value)) 776 | else: 777 | self._verticalSizeClass = self.element.GetChildMemberWithName(name) 778 | return self._verticalSizeClass 779 | 780 | @property 781 | def vertical_size_class_value(self): 782 | """ 783 | :return: XCUIElement vertical size class value 784 | :rtype: int 785 | """ 786 | return int(self.vertical_size_class.GetValue()) 787 | 788 | @property 789 | def vertical_size_class_summary(self): 790 | """ 791 | :return: XCUIElement vertical size class summary 792 | """ 793 | return self.get_user_interface_size_class_string(self.vertical_size_class_value) 794 | 795 | @property 796 | def uniquely_identifying_objective_c_code(self): 797 | """ 798 | :return: XCUIElement uniquely identifying Objective-C code 799 | :rtype: lldb.SBValue 800 | """ 801 | return fb.evaluateExpressionValue("(id)[{} _uniquelyIdentifyingObjectiveCCode]".format(self.element_value)) 802 | 803 | @property 804 | def uniquely_identifying_objective_c_code_value(self): 805 | """ 806 | :return: XCUIElement uniquely identifying Objective-C code value 807 | :rtype: str 808 | """ 809 | return normalize_array_description(self.uniquely_identifying_objective_c_code.GetObjectDescription()) 810 | 811 | @property 812 | def uniquely_identifying_swift_code(self): 813 | """ 814 | :return: XCUIElement uniquely identifying Swift code 815 | :rtype: lldb.SBValue 816 | """ 817 | return fb.evaluateExpressionValue("(id)[{} _uniquelyIdentifyingSwiftCode]".format(self.element_value)) 818 | 819 | @property 820 | def uniquely_identifying_swift_code_value(self): 821 | """ 822 | :return: XCUIElement uniquely identifying Swift code value 823 | :rtype: str 824 | """ 825 | return normalize_array_description(self.uniquely_identifying_swift_code.GetObjectDescription()) 826 | 827 | @property 828 | def is_touch_bar_element(self): 829 | """ 830 | :return: XCUIElement is touch bar element 831 | :rtype: lldb.SBValue 832 | """ 833 | return fb.evaluateExpressionValue("(BOOL)[{} isTouchBarElement]".format(self.element_value)) 834 | 835 | @property 836 | def is_touch_bar_element_value(self): 837 | """ 838 | :return: XCUIElement is touch bar element value 839 | :rtype: bool 840 | """ 841 | return bool(self.is_touch_bar_element.GetValueAsSigned()) 842 | 843 | @property 844 | def is_top_level_touch_bar_element(self): 845 | """ 846 | :return: XCUIElement is top level touch bar element 847 | :rtype: lldb.SBValue 848 | """ 849 | return fb.evaluateExpressionValue("(BOOL)[{} isTopLevelTouchBarElement]".format(self.element_value)) 850 | 851 | @property 852 | def is_top_level_touch_bar_element_value(self): 853 | """ 854 | :return: XCUIElement is top level touch bar element value 855 | :rtype: bool 856 | """ 857 | return bool(self.is_top_level_touch_bar_element.GetValueAsSigned()) 858 | 859 | @property 860 | def suggested_hit_points(self): 861 | """ 862 | :return: XCUIElement suggested hit points 863 | :rtype: lldb.SBValue 864 | """ 865 | return fb.evaluateExpressionValue("(NSArray *)[{} suggestedHitpoints]".format(self.element_value)) 866 | 867 | @property 868 | def suggested_hit_points_value(self): 869 | """ 870 | :return: XCUIElement suggested hit points 871 | :rtype: str 872 | """ 873 | return normalize_array_description(self.suggested_hit_points.GetObjectDescription()) 874 | 875 | @property 876 | def visible_frame(self): 877 | """ 878 | :return: XCUIElement visible frame 879 | :rtype: lldb.SBValue 880 | """ 881 | import_uikit() 882 | return fb.evaluateExpressionValue("(CGRect)[{} visibleFrame]".format(self.element_value)) 883 | 884 | @property 885 | def visible_frame_summary(self): 886 | """ 887 | :return: XCUIElement visible frame 888 | :rtype: str 889 | """ 890 | return CGRect(self.visible_frame).summary() 891 | 892 | @property 893 | def depth(self): 894 | """ 895 | :return: XCUIElement depth 896 | :rtype: lldb.SBValue 897 | """ 898 | return fb.evaluateExpressionValue("(int)[{} depth]".format(self.element_value)) 899 | 900 | @property 901 | def depth_value(self): 902 | """ 903 | :return: XCUIElement depth 904 | :rtype: int 905 | """ 906 | return int(self.depth.GetValue()) 907 | 908 | @property 909 | def hit_point(self): 910 | """ 911 | :return: XCUIElement hit point 912 | :rtype: lldb.SBValue 913 | """ 914 | import_uikit() 915 | return fb.evaluateExpressionValue("(CGPoint)[{} hitPoint]".format(self.element_value)) 916 | 917 | @property 918 | def hit_point_value(self): 919 | """ 920 | :return: XCUIElement hit point 921 | :rtype: str 922 | """ 923 | return CGPoint(self.hit_point).summary() 924 | 925 | @property 926 | def hit_point_for_scrolling(self): 927 | """ 928 | :return: XCUIElement hit point for scrolling 929 | :rtype: lldb.SBValue 930 | """ 931 | import_uikit() 932 | return fb.evaluateExpressionValue("(CGPoint)[{} hitPointForScrolling]".format(self.element_value)) 933 | 934 | @property 935 | def hit_point_for_scrolling_value(self): 936 | """ 937 | :return: XCUIElement hit point for scrolling 938 | :rtype: str 939 | """ 940 | return CGPoint(self.hit_point_for_scrolling).summary() 941 | 942 | def summary(self, pointer=False, trait=False, frame=False): 943 | """ 944 | Returns XCElementSnapshot summary 945 | 946 | :param bool pointer: Print pointers 947 | :param bool trait: Print traits 948 | :param bool frame: Print frames 949 | :return: XCElementSnapshot summary 950 | :rtype: str 951 | """ 952 | type_text = self.type_summary 953 | if pointer: 954 | type_text += " {:#x}".format(int(self.element_value, 16)) 955 | if trait: 956 | type_text += " traits: {}({:#x})".format(self.traits_summary, self.traits_value) 957 | 958 | frame_text = self.frame_summary if frame else None 959 | identifier = self.identifier_summary 960 | label = self.label_summary 961 | title = self.title_summary 962 | value = self.value_summary 963 | placeholder = self.placeholder_summary 964 | enabled = self.enabled_summary 965 | selected = self.selected_summary 966 | main_window = self.is_main_window_summary 967 | keyboard_focus = self.keyboard_focus_summary 968 | focus = self.focus_summary 969 | 970 | texts = [t for t in 971 | [frame_text, identifier, label, title, value, placeholder, 972 | enabled, selected, main_window, keyboard_focus, focus] 973 | if t is not None] 974 | 975 | return "{}: {}".format(type_text, ", ".join(texts)) 976 | 977 | def detail_summary(self): 978 | """ 979 | Returns XCElementSnapshot detail summary 980 | 981 | :return: XCElementSnapshot detail summary 982 | :rtype: str 983 | """ 984 | texts = list() 985 | texts.append("Pointer: {:#x}".format(int(self.element_value, 16))) 986 | texts.append("Type: {}".format(self.type_summary)) 987 | texts.append("Depth: {}".format(self.depth_value)) 988 | texts.append("Traits: {} ({:#x})".format(self.traits_summary, self.traits_value)) 989 | texts.append("Frame: {}".format(self.frame_summary)) 990 | texts.append("Visible frame: {}".format(self.visible_frame_summary)) 991 | texts.append("Identifier: '{}'".format(self.identifier_value)) 992 | texts.append("Label: '{}'".format(self.label_value)) 993 | texts.append("Title: '{}'".format(self.title_value)) 994 | texts.append("Value: '{}'".format(self.value_value)) 995 | texts.append("Placeholder: '{}'".format(self.placeholder_value)) 996 | if self.language != lldb.eLanguageTypeSwift: 997 | # They doesn't work on Swift :( 998 | texts.append("Hit point: {}".format(self.hit_point_value)) 999 | texts.append("Hit point for scrolling: {}".format(self.hit_point_for_scrolling_value)) 1000 | texts.append("Enabled: {}".format(self.enabled_value)) 1001 | texts.append("Selected: {}".format(self.selected_value)) 1002 | texts.append("Main Window: {}".format(self.is_main_window_value)) 1003 | texts.append("Keyboard focus: {}".format(self.keyboard_focus_value)) 1004 | texts.append("Focus: {}".format(self.focus_value)) 1005 | texts.append("Generation: {}".format(self.generation_value)) 1006 | texts.append("Horizontal size class: {}".format(self.horizontal_size_class_summary)) 1007 | texts.append("Vertical size class: {}".format(self.vertical_size_class_summary)) 1008 | texts.append("TouchBar element: {}".format(self.is_touch_bar_element_value)) 1009 | texts.append("TouchBar top level element: {}".format(self.is_top_level_touch_bar_element_value)) 1010 | texts.append("Unique Objective-C: {}".format(self.uniquely_identifying_objective_c_code_value)) 1011 | texts.append("Unique Swift: {}".format(self.uniquely_identifying_swift_code_value)) 1012 | texts.append("Suggested hit points: {}".format(self.suggested_hit_points_value)) 1013 | return "\n".join(texts) 1014 | 1015 | def tree(self): 1016 | """ 1017 | Returns tree of elements in hierarchy 1018 | 1019 | :return: Elements hierarchy 1020 | :rtype: _ElementList 1021 | """ 1022 | children = [XCElementSnapshot(e, self.language).tree() for e in self.children_list] 1023 | return _ElementList(self, children) 1024 | 1025 | def find_missing_identifiers(self, status_bar): 1026 | """ 1027 | Find element which has a label but doesn't have an identifier 1028 | 1029 | :param bool status_bar: Print status bar items 1030 | :return: Hierarchy structure with items which has a label but doesn't have an identifier 1031 | :rtype: _ElementList | None 1032 | """ 1033 | # Do not print status bar items 1034 | if status_bar is not True and self.type_value == XCUIElementType.StatusBar: 1035 | return None 1036 | 1037 | children_missing = [XCElementSnapshot(e, self.language).find_missing_identifiers(status_bar=status_bar) for e in self.children_list] 1038 | children_missing = [x for x in children_missing if x is not None] 1039 | 1040 | # Self and its children are not missing identifiers 1041 | if self.is_missing_identifier is False and len(children_missing) == 0: 1042 | return None 1043 | 1044 | return _ElementList(self, children_missing) 1045 | 1046 | @staticmethod 1047 | def get_type_value_string(value): 1048 | """ 1049 | Get element type string from XCUIElementType (as int) 1050 | 1051 | :param int value: XCUIElementType (as int) 1052 | :return: XCUIElementType string 1053 | :rtype: str 1054 | """ 1055 | return XCUIElementType.name_for_value(value) 1056 | 1057 | @staticmethod 1058 | def get_traits_value_string(value): 1059 | """ 1060 | Get element traits string from UIAccessibilityTraits (as int) 1061 | 1062 | :param int value: UIAccessibilityTraits (as int) 1063 | :return: UIAccessibilityTraits string 1064 | :rtype: str 1065 | """ 1066 | return UIAccessibilityTraits.name_for_value(value) 1067 | 1068 | @staticmethod 1069 | def get_user_interface_size_class_string(value): 1070 | """ 1071 | Get user interface size class string from UIUserInterfaceSizeClass (as int) 1072 | 1073 | :param value: UIAccessibilityTraits (as int) 1074 | :return: UIUserInterfaceSizeClass string 1075 | :rtype: str 1076 | """ 1077 | return UIUserInterfaceSizeClass.name_for_value(value) 1078 | 1079 | 1080 | class XCUIElementType(object): 1081 | """ 1082 | Represents all XCUIElementType types 1083 | """ 1084 | Any = 0 1085 | Other = 1 1086 | Application = 2 1087 | Group = 3 1088 | Window = 4 1089 | Sheet = 5 1090 | Drawer = 6 1091 | Alert = 7 1092 | Dialog = 8 1093 | Button = 9 1094 | RadioButton = 10 1095 | RadioGroup = 11 1096 | CheckBox = 12 1097 | DisclosureTriangle = 13 1098 | PopUpButton = 14 1099 | ComboBox = 15 1100 | MenuButton = 16 1101 | ToolbarButton = 17 1102 | Popover = 18 1103 | Keyboard = 19 1104 | Key = 20 1105 | NavigationBar = 21 1106 | TabBar = 22 1107 | TabGroup = 23 1108 | Toolbar = 24 1109 | StatusBar = 25 1110 | Table = 26 1111 | TableRow = 27 1112 | TableColumn = 28 1113 | Outline = 29 1114 | OutlineRow = 30 1115 | Browser = 31 1116 | CollectionView = 32 1117 | Slider = 33 1118 | PageIndicator = 34 1119 | ProgressIndicator = 35 1120 | ActivityIndicator = 36 1121 | SegmentedControl = 37 1122 | Picker = 38 1123 | PickerWheel = 39 1124 | Switch = 40 1125 | Toggle = 41 1126 | Link = 42 1127 | Image = 43 1128 | Icon = 44 1129 | SearchField = 45 1130 | ScrollView = 46 1131 | ScrollBar = 47 1132 | StaticText = 48 1133 | TextField = 49 1134 | SecureTextField = 50 1135 | DatePicker = 51 1136 | TextView = 52 1137 | Menu = 53 1138 | MenuItem = 54 1139 | MenuBar = 55 1140 | MenuBarItem = 56 1141 | Map = 57 1142 | WebView = 58 1143 | IncrementArrow = 59 1144 | DecrementArrow = 60 1145 | Timeline = 61 1146 | RatingIndicator = 62 1147 | ValueIndicator = 63 1148 | SplitGroup = 64 1149 | Splitter = 65 1150 | RelevanceIndicator = 66 1151 | ColorWell = 67 1152 | HelpTag = 68 1153 | Matte = 69 1154 | DockItem = 70 1155 | Ruler = 71 1156 | RulerMarker = 72 1157 | Grid = 73 1158 | LevelIndicator = 74 1159 | Cell = 75 1160 | LayoutArea = 76 1161 | LayoutItem = 77 1162 | Handle = 78 1163 | Stepper = 79 1164 | Tab = 80 1165 | TouchBar = 81 1166 | 1167 | @classmethod 1168 | def _attributes_by_value(cls): 1169 | """ 1170 | :return: Hash of all attributes and their values 1171 | :rtype: dict[int, str] 1172 | """ 1173 | class_attributes = set(dir(cls)) - set(dir(object)) 1174 | return dict([(getattr(cls, n), n) for n in class_attributes if not callable(getattr(cls, n)) and not n.startswith("__")]) 1175 | 1176 | @classmethod 1177 | def name_for_value(cls, value): 1178 | """ 1179 | Get element type string from XCUIElementType (as int) 1180 | 1181 | :param int value: XCUIElementType (as int) 1182 | :return: Name of type 1183 | :rtype: str 1184 | """ 1185 | attributes = cls._attributes_by_value() 1186 | if value in attributes: 1187 | return attributes[value] 1188 | else: 1189 | return "Unknown ({:#x})".format(value) 1190 | 1191 | 1192 | class UIAccessibilityTraits(object): 1193 | """ 1194 | Represents all UIAccessibilityTraits types 1195 | """ 1196 | Button = 0x0000000000000001 1197 | Link = 0x0000000000000002 1198 | Image = 0x0000000000000004 1199 | Selected = 0x0000000000000008 1200 | PlaysSound = 0x0000000000000010 1201 | KeyboardKey = 0x0000000000000020 1202 | StaticText = 0x0000000000000040 1203 | SummaryElement = 0x0000000000000080 1204 | NotEnabled = 0x0000000000000100 1205 | UpdatesFrequently = 0x0000000000000200 1206 | SearchField = 0x0000000000000400 1207 | StartsMediaSession = 0x0000000000000800 1208 | Adjustable = 0x0000000000001000 1209 | AllowsDirectInteraction = 0x0000000000002000 1210 | CausesPageTurn = 0x0000000000004000 1211 | TabBar = 0x0000000000008000 1212 | Header = 0x0000000000010000 1213 | 1214 | @classmethod 1215 | def _attributes_by_value(cls): 1216 | """ 1217 | :return: Hash of all attributes and their values 1218 | :rtype: dict[int, str] 1219 | """ 1220 | class_attributes = set(dir(cls)) - set(dir(object)) 1221 | return dict([(getattr(cls, n), n) for n in class_attributes if not callable(getattr(cls, n)) and not n.startswith("__")]) 1222 | 1223 | @classmethod 1224 | def name_for_value(cls, value): 1225 | """ 1226 | Get element traits string from UIAccessibilityTraits (as int) 1227 | 1228 | :param int value: UIAccessibilityTraits (as int) 1229 | :return: UIAccessibilityTraits string 1230 | :rtype: str 1231 | """ 1232 | if value == 0: 1233 | return "None" 1234 | 1235 | traits = [] 1236 | attributes = cls._attributes_by_value() 1237 | for k in attributes.keys(): 1238 | if value & k: 1239 | traits.append(attributes[k]) 1240 | 1241 | if len(traits) == 0: 1242 | return "Unknown" 1243 | else: 1244 | return ", ".join(traits) 1245 | 1246 | 1247 | class UIUserInterfaceSizeClass(object): 1248 | """ 1249 | Represents all UIUserInterfaceSizeClass types 1250 | """ 1251 | Unspecified = 0 1252 | Compact = 1 1253 | Regular = 2 1254 | 1255 | @classmethod 1256 | def name_for_value(cls, value): 1257 | """ 1258 | Get user interface size class string from UIUserInterfaceSizeClass (as int) 1259 | 1260 | :param int value: UIAccessibilityTraits (as int) 1261 | :return: UIUserInterfaceSizeClass string 1262 | :rtype: str 1263 | """ 1264 | if value == cls.Unspecified: 1265 | return "Unspecified" 1266 | elif value == cls.Compact: 1267 | return "Compact" 1268 | elif value == cls.Regular: 1269 | return "Regular" 1270 | else: 1271 | return "Unknown ({:#x})".format(value) 1272 | 1273 | 1274 | class CGRect(object): 1275 | """ 1276 | CGRect wrapper 1277 | 1278 | :param lldb.SBValue element: CGRect object 1279 | """ 1280 | 1281 | def __init__(self, element): 1282 | """ 1283 | :param lldb.SBValue element: CGRect object 1284 | """ 1285 | super(CGRect, self).__init__() 1286 | 1287 | self.element = element 1288 | 1289 | def summary(self): 1290 | """ 1291 | :return: CGRect summary 1292 | :rtype: str 1293 | """ 1294 | origin_element = self.element.GetChildMemberWithName("origin") 1295 | origin = CGPoint(origin_element) 1296 | 1297 | size = self.element.GetChildMemberWithName("size") 1298 | width = size.GetChildMemberWithName("width") 1299 | height = size.GetChildMemberWithName("height") 1300 | 1301 | width_value = float(width.GetValue()) 1302 | height_value = float(height.GetValue()) 1303 | return "{{{}, {{{}, {}}}}}".format(origin.summary(), width_value, height_value) 1304 | 1305 | 1306 | class CGPoint(object): 1307 | """ 1308 | CGPoint wrapper 1309 | 1310 | :param lldb.SBValue element: CGPoint object 1311 | """ 1312 | 1313 | def __init__(self, element): 1314 | super(CGPoint, self).__init__() 1315 | 1316 | self.element = element 1317 | 1318 | def summary(self): 1319 | """ 1320 | :return: CGPoint summary 1321 | :rtype: str 1322 | """ 1323 | x = self.element.GetChildMemberWithName("x") 1324 | y = self.element.GetChildMemberWithName("y") 1325 | 1326 | x_value = float(x.GetValue()) 1327 | y_value = float(y.GetValue()) 1328 | return "{{{}, {}}}".format(x_value, y_value) 1329 | 1330 | 1331 | def normalize_summary(summary): 1332 | """ 1333 | Normalize summary by removing "'" and "@" characters 1334 | 1335 | :param str summary: Summary string to normalize 1336 | :return: Normalized summary string 1337 | :rtype: str 1338 | """ 1339 | return summary \ 1340 | .lstrip("@") \ 1341 | .strip("\"") 1342 | 1343 | 1344 | def normalize_array_description(description): 1345 | """ 1346 | Normalize array object description by removing "<" and ">" characters and content between them. 1347 | 1348 | :param str description: Array object description 1349 | :return: Normalized array object description string 1350 | :rtype: str 1351 | """ 1352 | return re.sub("^(<.*>)", "", description).strip() 1353 | 1354 | 1355 | _uikit_imported = False 1356 | def import_uikit(): 1357 | """ 1358 | Import UIKit framework to the debugger 1359 | """ 1360 | global _uikit_imported 1361 | if _uikit_imported: 1362 | return 1363 | _uikit_imported = True 1364 | fb.evaluateExpressionValue("@import UIKit") 1365 | 1366 | 1367 | def debug(element): 1368 | """ 1369 | Debug helper 1370 | 1371 | :param lldb.SBValue element: Element to debug 1372 | """ 1373 | print("---") 1374 | print("element: {}".format(element)) 1375 | print("element class: {}".format(element.__class__)) 1376 | print("element name: {}".format(element.GetName())) 1377 | print("element type name: {}".format(element.GetTypeName())) 1378 | print("element value: {}".format(element.GetValue())) 1379 | print("element value class: {}".format(element.GetValue().__class__)) 1380 | print("element value type: {}".format(element.GetValueType())) 1381 | print("element value signed: {0}({0:#x})".format(element.GetValueAsSigned())) 1382 | print("element value unsigned: {0}({0:#x})".format(element.GetValueAsUnsigned())) 1383 | print("element summary: {}".format(element.GetSummary())) 1384 | print("element description: {}".format(element.GetObjectDescription())) 1385 | print("element children num: {}".format(element.GetNumChildren())) 1386 | for i in range(0, element.GetNumChildren()): 1387 | child = element.GetChildAtIndex(i) 1388 | """:type: lldb.SBValue""" 1389 | print("element child {:02}: {}".format(i, child.GetName())) 1390 | print("===") 1391 | -------------------------------------------------------------------------------- /libexec/fblldb.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Copyright (c) 2014, Facebook, Inc. 4 | # All rights reserved. 5 | # 6 | # This source code is licensed under the BSD-style license found in the 7 | # LICENSE file in the root directory of this source tree. An additional grant 8 | # of patent rights can be found in the PATENTS file in the same directory. 9 | 10 | import lldb 11 | 12 | import imp 13 | import os 14 | 15 | from optparse import OptionParser 16 | 17 | import fblldbbase as fb 18 | 19 | def __lldb_init_module(debugger, dict): 20 | filePath = os.path.realpath(__file__) 21 | lldbHelperDir = os.path.dirname(filePath) 22 | 23 | commandsDirectory = os.path.join(lldbHelperDir, 'commands') 24 | loadCommandsInDirectory(commandsDirectory) 25 | 26 | def loadCommandsInDirectory(commandsDirectory): 27 | for file in os.listdir(commandsDirectory): 28 | fileName, fileExtension = os.path.splitext(file) 29 | if fileExtension == '.py': 30 | module = imp.load_source(fileName, os.path.join(commandsDirectory, file)) 31 | 32 | if hasattr(module, 'lldbinit'): 33 | module.lldbinit() 34 | 35 | if hasattr(module, 'lldbcommands'): 36 | module._loadedFunctions = {} 37 | for command in module.lldbcommands(): 38 | loadCommand(module, command, commandsDirectory, fileName, fileExtension) 39 | 40 | def loadCommand(module, command, directory, filename, extension): 41 | func = makeRunCommand(command, os.path.join(directory, filename + extension)) 42 | name = command.name() 43 | helpText = command.description().strip().splitlines()[0] # first line of description 44 | 45 | key = filename + '_' + name 46 | 47 | module._loadedFunctions[key] = func 48 | 49 | functionName = '__' + key 50 | 51 | lldb.debugger.HandleCommand('script ' + functionName + ' = sys.modules[\'' + module.__name__ + '\']._loadedFunctions[\'' + key + '\']') 52 | lldb.debugger.HandleCommand('command script add --help "{help}" --function {function} {name}'.format( 53 | help=helpText.replace('"', '\\"'), # escape quotes 54 | function=functionName, 55 | name=name)) 56 | 57 | def makeRunCommand(command, filename): 58 | def runCommand(debugger, input, exe_ctx, result, _): 59 | command.result = result 60 | command.context = exe_ctx 61 | splitInput = command.lex(input) 62 | 63 | # OptionParser will throw in the case where you want just one big long argument and no 64 | # options and you enter something that starts with '-' in the argument. e.g.: 65 | # somecommand -[SomeClass someSelector:] 66 | # This solves that problem by prepending a '--' so that OptionParser does the right 67 | # thing. 68 | options = command.options() 69 | if len(options) == 0: 70 | if '--' not in splitInput: 71 | splitInput.insert(0, '--') 72 | 73 | parser = optionParserForCommand(command) 74 | (options, args) = parser.parse_args(splitInput) 75 | 76 | # When there are more args than the command has declared, assume 77 | # the initial args form an expression and combine them into a single arg. 78 | if len(args) > len(command.args()): 79 | overhead = len(args) - len(command.args()) 80 | head = args[:overhead + 1] # Take N+1 and reduce to 1. 81 | args = [' '.join(head)] + args[-overhead:] 82 | 83 | if validateArgsForCommand(args, command): 84 | command.run(args, options) 85 | 86 | runCommand.__doc__ = helpForCommand(command, filename) 87 | return runCommand 88 | 89 | def validateArgsForCommand(args, command): 90 | if len(args) < len(command.args()): 91 | defaultArgs = [arg.default for arg in command.args()] 92 | defaultArgsToAppend = defaultArgs[len(args):] 93 | 94 | index = len(args) 95 | for defaultArg in defaultArgsToAppend: 96 | if not defaultArg: 97 | arg = command.args()[index] 98 | # print 'Whoops! You are missing the <' + arg.argName + '> argument.' 99 | print("Whoops! You are missing the < %s > argument." %(arg.argName)) 100 | # print '\nUsage: ' + usageForCommand(command) 101 | print ("\nUsage: %s" %(usageForCommand(command))) 102 | return 103 | index += 1 104 | 105 | args.extend(defaultArgsToAppend) 106 | return True 107 | 108 | def optionParserForCommand(command): 109 | parser = OptionParser() 110 | 111 | for argument in command.options(): 112 | if argument.boolean: 113 | parser.add_option(argument.shortName, argument.longName, dest=argument.argName, 114 | help=argument.help, action=("store_false" if argument.default else "store_true")) 115 | else: 116 | parser.add_option(argument.shortName, argument.longName, dest=argument.argName, 117 | help=argument.help, default=argument.default) 118 | 119 | return parser 120 | 121 | def helpForCommand(command, filename): 122 | help = command.description() 123 | 124 | argSyntax = '' 125 | optionSyntax = '' 126 | 127 | if command.args(): 128 | help += '\n\nArguments:' 129 | for arg in command.args(): 130 | help += '\n <' + arg.argName + '>; ' 131 | if arg.argType: 132 | help += 'Type: ' + arg.argType + '; ' 133 | help += arg.help 134 | argSyntax += ' <' + arg.argName + '>' 135 | 136 | if command.options(): 137 | help += '\n\nOptions:' 138 | for option in command.options(): 139 | 140 | if option.longName and option.shortName: 141 | optionFlag = option.longName + '/' + option.shortName 142 | elif option.longName: 143 | optionFlag = option.longName 144 | else: 145 | optionFlag = option.shortName 146 | 147 | help += '\n ' + optionFlag + ' ' 148 | 149 | if not option.boolean: 150 | help += '<' + option.argName + '>; Type: ' + option.argType 151 | 152 | help += '; ' + option.help 153 | 154 | optionSyntax += ' [{name}{arg}]'.format( 155 | name=(option.longName or option.shortName), 156 | arg=('' if option.boolean else ('=' + option.argName)) 157 | ) 158 | 159 | help += '\n\nSyntax: ' + command.name() + optionSyntax + argSyntax 160 | 161 | help += '\n\nThis command is implemented as %s in %s.' % (command.__class__.__name__, filename) 162 | 163 | return help 164 | 165 | def usageForCommand(command): 166 | usage = command.name() 167 | for arg in command.args(): 168 | if arg.default: 169 | usage += ' [' + arg.argName + ']' 170 | else: 171 | usage += ' ' + arg.argName 172 | 173 | return usage 174 | -------------------------------------------------------------------------------- /libexec/fblldbbase.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Copyright (c) 2014, Facebook, Inc. 4 | # All rights reserved. 5 | # 6 | # This source code is licensed under the BSD-style license found in the 7 | # LICENSE file in the root directory of this source tree. An additional grant 8 | # of patent rights can be found in the PATENTS file in the same directory. 9 | 10 | import lldb 11 | import json 12 | import shlex 13 | 14 | class FBCommandArgument: 15 | def __init__(self, short='', long='', arg='', type='', help='', default='', boolean=False): 16 | self.shortName = short 17 | self.longName = long 18 | self.argName = arg 19 | self.argType = type 20 | self.help = help 21 | self.default = default 22 | self.boolean = boolean 23 | 24 | class FBCommand: 25 | def name(self): 26 | return None 27 | 28 | def options(self): 29 | return [] 30 | 31 | def args(self): 32 | return [] 33 | 34 | def description(self): 35 | return '' 36 | 37 | def lex(self, commandLine): 38 | return shlex.split(commandLine) 39 | 40 | def run(self, arguments, option): 41 | pass 42 | 43 | def isSuccess(error): 44 | # When evaluating a `void` expression, the returned value will indicate an 45 | # error. This error is named: kNoResult. This error value does *not* mean 46 | # there was a problem. This logic follows what the builtin `expression` 47 | # command does. See: https://git.io/vwpjl (UserExpression.h) 48 | kNoResult = 0x1001 49 | return error.success or error.value == kNoResult 50 | 51 | def importModule(frame, module): 52 | options = lldb.SBExpressionOptions() 53 | options.SetLanguage(lldb.eLanguageTypeObjC) 54 | value = frame.EvaluateExpression('@import ' + module, options) 55 | return isSuccess(value.error) 56 | 57 | # evaluates expression in Objective-C++ context, so it will work even for 58 | # Swift projects 59 | def evaluateExpressionValue(expression, printErrors=True, language=lldb.eLanguageTypeObjC_plus_plus, tryAllThreads=False): 60 | frame = lldb.debugger.GetSelectedTarget().GetProcess().GetSelectedThread().GetSelectedFrame() 61 | options = lldb.SBExpressionOptions() 62 | options.SetLanguage(language) 63 | 64 | # Allow evaluation that contains a @throw/@catch. 65 | # By default, ObjC @throw will cause evaluation to be aborted. At the time 66 | # of a @throw, it's not known if the exception will be handled by a @catch. 67 | # An exception that's caught, should not cause evaluation to fail. 68 | options.SetTrapExceptions(False) 69 | 70 | # Give evaluation more time. 71 | options.SetTimeoutInMicroSeconds(5000000) # 5s 72 | 73 | # Most Chisel commands are not multithreaded. 74 | options.SetTryAllThreads(tryAllThreads) 75 | 76 | value = frame.EvaluateExpression(expression, options) 77 | error = value.GetError() 78 | 79 | # Retry if the error could be resolved by first importing UIKit. 80 | if (error.type == lldb.eErrorTypeExpression and 81 | error.value == lldb.eExpressionParseError and 82 | importModule(frame, 'UIKit')): 83 | value = frame.EvaluateExpression(expression, options) 84 | error = value.GetError() 85 | 86 | if printErrors and not isSuccess(error): 87 | print(error) 88 | 89 | return value 90 | 91 | def evaluateInputExpression(expression, printErrors=True): 92 | # HACK 93 | if expression.startswith('(id)'): 94 | return evaluateExpressionValue(expression, printErrors=printErrors).GetValue() 95 | 96 | frame = lldb.debugger.GetSelectedTarget().GetProcess().GetSelectedThread().GetSelectedFrame() 97 | options = lldb.SBExpressionOptions() 98 | options.SetTrapExceptions(False) 99 | value = frame.EvaluateExpression(expression, options) 100 | error = value.GetError() 101 | 102 | if printErrors and error.Fail(): 103 | print(error) 104 | 105 | return value.GetValue() 106 | 107 | def evaluateIntegerExpression(expression, printErrors=True): 108 | output = evaluateExpression('(int)(' + expression + ')', printErrors).replace('\'', '') 109 | if output.startswith('\\x'): # Booleans may display as \x01 (Hex) 110 | output = output[2:] 111 | elif output.startswith('\\'): # Or as \0 (Dec) 112 | output = output[1:] 113 | return int(output, 0) 114 | 115 | def evaluateBooleanExpression(expression, printErrors=True): 116 | return (int(evaluateIntegerExpression('(BOOL)(' + expression + ')', printErrors)) != 0) 117 | 118 | def evaluateExpression(expression, printErrors=True): 119 | return evaluateExpressionValue(expression, printErrors=printErrors).GetValue() 120 | 121 | def describeObject(expression, printErrors=True): 122 | return evaluateExpressionValue('(id)(' + expression + ')', printErrors).GetObjectDescription() 123 | 124 | def evaluateEffect(expression, printErrors=True): 125 | evaluateExpressionValue('(void)(' + expression + ')', printErrors=printErrors) 126 | 127 | def evaluateObjectExpression(expression, printErrors=True): 128 | return evaluateExpression('(id)(' + expression + ')', printErrors) 129 | 130 | def evaluateCStringExpression(expression, printErrors=True): 131 | ret = evaluateExpression(expression, printErrors) 132 | 133 | process = lldb.debugger.GetSelectedTarget().GetProcess() 134 | error = lldb.SBError() 135 | ret = process.ReadCStringFromMemory(int(ret, 16), 256, error) 136 | if error.Success(): 137 | return ret 138 | else: 139 | if printErrors: 140 | print(error) 141 | return None 142 | 143 | 144 | RETURN_MACRO = """ 145 | #define IS_JSON_OBJ(obj)\ 146 | (obj != nil && ((bool)[NSJSONSerialization isValidJSONObject:obj] ||\ 147 | (bool)[obj isKindOfClass:[NSString class]] ||\ 148 | (bool)[obj isKindOfClass:[NSNumber class]])) 149 | #define RETURN(ret) ({\ 150 | if (!IS_JSON_OBJ(ret)) {\ 151 | (void)[NSException raise:@"Invalid RETURN argument" format:@""];\ 152 | }\ 153 | NSDictionary *__dict = @{@"return":ret};\ 154 | NSData *__data = (id)[NSJSONSerialization dataWithJSONObject:__dict options:0 error:NULL];\ 155 | NSString *__str = (id)[[NSString alloc] initWithData:__data encoding:4];\ 156 | (char *)[__str UTF8String];}) 157 | #define RETURNCString(ret)\ 158 | ({NSString *___cstring_ret = [NSString stringWithUTF8String:ret];\ 159 | RETURN(___cstring_ret);}) 160 | """ 161 | 162 | def check_expr(expr): 163 | return expr.strip().split(';')[-2].find('RETURN') != -1 164 | 165 | # evaluate a batch of Objective-C expressions, the last expression must contain a RETURN marco 166 | # and it will automatic transform the Objective-C object to Python object 167 | # Example: 168 | # >>> fblldbbase.evaluate('NSString *str = @"hello world"; RETURN(@{@"key": str});') 169 | # {u'key': u'hello world'} 170 | def evaluate(expr): 171 | if not check_expr(expr): 172 | raise Exception("Invalid Expression, the last expression not include a RETURN family marco") 173 | 174 | command = "({" + RETURN_MACRO + '\n' + expr + "})" 175 | ret = evaluateExpressionValue(command, printErrors=True) 176 | if not ret.GetError().Success(): 177 | print(ret.GetError()) 178 | return None 179 | else: 180 | process = lldb.debugger.GetSelectedTarget().GetProcess() 181 | error = lldb.SBError() 182 | ret = process.ReadCStringFromMemory(int(ret.GetValue(), 16), 2**20, error) 183 | if not error.Success(): 184 | print(error) 185 | return None 186 | else: 187 | ret = json.loads(ret) 188 | return ret['return'] 189 | 190 | def currentLanguage(): 191 | return lldb.debugger.GetSelectedTarget().GetProcess().GetSelectedThread().GetSelectedFrame().GetCompileUnit().GetLanguage() 192 | -------------------------------------------------------------------------------- /libexec/fblldbinputhelpers.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Copyright (c) 2014, Facebook, Inc. 4 | # All rights reserved. 5 | # 6 | # This source code is licensed under the BSD-style license found in the 7 | # LICENSE file in the root directory of this source tree. An additional grant 8 | # of patent rights can be found in the PATENTS file in the same directory. 9 | 10 | import lldb 11 | 12 | class FBInputHandler: 13 | def __init__(self, debugger, callback): 14 | self.debugger = debugger 15 | self.callback = callback 16 | self.inputReader = lldb.SBInputReader() 17 | self.inputReader.Initialize( 18 | debugger, 19 | self.handleInput, 20 | lldb.eInputReaderGranularityLine, 21 | None, 22 | None, # prompt 23 | True # echo 24 | ) 25 | 26 | def isValid(self): 27 | return not self.inputReader.IsDone() 28 | 29 | def start(self): 30 | self.debugger.PushInputReader(self.inputReader) 31 | 32 | def stop(self): 33 | self.inputReader.SetIsDone(True) 34 | 35 | def handleInput(self, inputReader, notification, bytes): 36 | if notification == lldb.eInputReaderGotToken: 37 | self.callback(bytes) 38 | elif notification == lldb.eInputReaderInterrupt: 39 | self.stop() 40 | 41 | return len(bytes) 42 | -------------------------------------------------------------------------------- /libexec/fblldbobjcruntimehelpers.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Copyright (c) 2014, Facebook, Inc. 4 | # All rights reserved. 5 | # 6 | # This source code is licensed under the BSD-style license found in the 7 | # LICENSE file in the root directory of this source tree. An additional grant 8 | # of patent rights can be found in the PATENTS file in the same directory. 9 | 10 | import re 11 | 12 | import lldb 13 | import fblldbbase as fb 14 | 15 | def objc_getClass(className): 16 | command = '(void*)objc_getClass("{}")'.format(className) 17 | value = fb.evaluateExpression(command) 18 | return value 19 | 20 | def object_getClass(object): 21 | command = '(void*)object_getClass((id){})'.format(object) 22 | value = fb.evaluateExpression(command) 23 | return value 24 | 25 | def class_getName(klass): 26 | command = '(const char*)class_getName((Class){})'.format(klass) 27 | value = fb.evaluateExpressionValue(command).GetSummary().strip('"') 28 | return value 29 | 30 | def class_getSuperclass(klass): 31 | command = '(void*)class_getSuperclass((Class){})'.format(klass) 32 | value = fb.evaluateExpression(command) 33 | return value 34 | 35 | def class_isMetaClass(klass): 36 | command = 'class_isMetaClass((Class){})'.format(klass) 37 | return fb.evaluateBooleanExpression(command) 38 | 39 | def class_getInstanceMethod(klass, selector): 40 | command = '(void*)class_getInstanceMethod((Class){}, @selector({}))'.format(klass, selector) 41 | value = fb.evaluateExpression(command) 42 | return value 43 | 44 | def currentArch(): 45 | targetTriple = lldb.debugger.GetSelectedTarget().GetTriple() 46 | arch = targetTriple.split('-')[0] 47 | if arch == 'x86_64h': 48 | arch = 'x86_64' 49 | return arch 50 | 51 | def functionPreambleExpressionForSelf(): 52 | import re 53 | 54 | arch = currentArch() 55 | expressionForSelf = None 56 | if arch == 'i386': 57 | expressionForSelf = '*(id*)($esp+4)' 58 | elif arch == 'x86_64': 59 | expressionForSelf = '(id)$rdi' 60 | elif arch == 'arm64': 61 | expressionForSelf = '(id)$x0' 62 | elif re.match(r'^armv.*$', arch): 63 | expressionForSelf = '(id)$r0' 64 | return expressionForSelf 65 | 66 | def functionPreambleExpressionForObjectParameterAtIndex(parameterIndex): 67 | arch = currentArch() 68 | expresssion = None 69 | if arch == 'i386': 70 | expresssion = '*(id*)($esp + ' + str(12 + parameterIndex * 4) + ')' 71 | elif arch == 'x86_64': 72 | if parameterIndex > 3: 73 | raise Exception("Current implementation can not return object at index greater than 3 for x86_64") 74 | registersList = ['rdx', 'rcx', 'r8', 'r9'] 75 | expresssion = '(id)$' + registersList[parameterIndex] 76 | elif arch == 'arm64': 77 | if parameterIndex > 5: 78 | raise Exception("Current implementation can not return object at index greater than 5 for arm64") 79 | expresssion = '(id)$x' + str(parameterIndex + 2) 80 | elif re.match(r'^armv.*$', arch): 81 | if parameterIndex > 1: 82 | raise Exception("Current implementation can not return object at index greater than 1 for arm32") 83 | expresssion = '(id)$r' + str(parameterIndex + 2) 84 | return expresssion 85 | 86 | def isMacintoshArch(): 87 | arch = currentArch() 88 | if not arch == 'x86_64': 89 | return False 90 | 91 | nsClassName = 'NSApplication' 92 | command = '(void*)objc_getClass("{}")'.format(nsClassName) 93 | 94 | return (fb.evaluateBooleanExpression(command + '!= nil')) 95 | 96 | def isIOSSimulator(): 97 | return fb.evaluateExpressionValue('(id)[[UIDevice currentDevice] model]').GetObjectDescription().lower().find('simulator') >= 0 98 | 99 | def isIOSDevice(): 100 | return not isMacintoshArch() and not isIOSSimulator() 101 | -------------------------------------------------------------------------------- /libexec/fblldbobjecthelpers.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Copyright (c) 2014, Facebook, Inc. 4 | # All rights reserved. 5 | # 6 | # This source code is licensed under the BSD-style license found in the 7 | # LICENSE file in the root directory of this source tree. An additional grant 8 | # of patent rights can be found in the PATENTS file in the same directory. 9 | 10 | import lldb 11 | import fblldbbase as fb 12 | 13 | def isKindOfClass(obj, className): 14 | isKindOfClassStr = '[(id)' + obj + ' isKindOfClass:[{} class]]' 15 | return fb.evaluateBooleanExpression(isKindOfClassStr.format(className)) 16 | 17 | def className(obj): 18 | return fb.evaluateExpressionValue('(id)[(' + obj + ') class]').GetObjectDescription() 19 | -------------------------------------------------------------------------------- /libexec/fblldbviewcontrollerhelpers.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Copyright (c) 2014, Facebook, Inc. 4 | # All rights reserved. 5 | # 6 | # This source code is licensed under the BSD-style license found in the 7 | # LICENSE file in the root directory of this source tree. An additional grant 8 | # of patent rights can be found in the PATENTS file in the same directory. 9 | 10 | import lldb 11 | 12 | import fblldbbase as fb 13 | import fblldbobjcruntimehelpers as runtimeHelpers 14 | 15 | def presentViewController(viewController): 16 | vc = '(%s)' % (viewController) 17 | 18 | if fb.evaluateBooleanExpression('%s != nil && ((BOOL)[(id)%s isKindOfClass:(Class)[UIViewController class]])' % (vc, vc)): 19 | notPresented = fb.evaluateBooleanExpression('[%s presentingViewController] == nil' % vc) 20 | 21 | if notPresented: 22 | fb.evaluateEffect('[[[[UIApplication sharedApplication] keyWindow] rootViewController] presentViewController:%s animated:YES completion:nil]' % vc) 23 | else: 24 | raise Exception('Argument is already presented') 25 | else: 26 | raise Exception('Argument must be a UIViewController') 27 | 28 | def dismissViewController(viewController): 29 | vc = '(%s)' % (viewController) 30 | 31 | if fb.evaluateBooleanExpression('%s != nil && ((BOOL)[(id)%s isKindOfClass:(Class)[UIViewController class]])' % (vc, vc)): 32 | isPresented = fb.evaluateBooleanExpression('[%s presentingViewController] != nil' % vc) 33 | 34 | if isPresented: 35 | fb.evaluateEffect('[(UIViewController *)%s dismissViewControllerAnimated:YES completion:nil]' % vc) 36 | else: 37 | raise Exception('Argument must be presented') 38 | else: 39 | raise Exception('Argument must be a UIViewController') 40 | 41 | def viewControllerRecursiveDescription(vc): 42 | return _recursiveViewControllerDescriptionWithPrefixAndChildPrefix(fb.evaluateObjectExpression(vc), '', '', '') 43 | 44 | def _viewControllerDescription(viewController): 45 | vc = '(%s)' % (viewController) 46 | 47 | if fb.evaluateBooleanExpression('[(id)%s isViewLoaded]' % (vc)): 48 | result = fb.evaluateExpressionValue('(id)[[NSString alloc] initWithFormat:@"<%%@: %%p; view = <%%@; %%p>; frame = (%%g, %%g; %%g, %%g)>", (id)NSStringFromClass((id)[(id)%s class]), %s, (id)[(id)[(id)%s view] class], (id)[(id)%s view], ((CGRect)[(id)[(id)%s view] frame]).origin.x, ((CGRect)[(id)[(id)%s view] frame]).origin.y, ((CGRect)[(id)[(id)%s view] frame]).size.width, ((CGRect)[(id)[(id)%s view] frame]).size.height]' % (vc, vc, vc, vc, vc, vc, vc, vc)) 49 | else: 50 | result = fb.evaluateExpressionValue('(id)[[NSString alloc] initWithFormat:@"<%%@: %%p; view not loaded>", (id)NSStringFromClass((id)[(id)%s class]), %s]' % (vc, vc)) 51 | 52 | if result.GetError() is not None and str(result.GetError()) != 'success': 53 | return '[Error getting description.]' 54 | else: 55 | return result.GetObjectDescription() 56 | 57 | 58 | def _recursiveViewControllerDescriptionWithPrefixAndChildPrefix(vc, string, prefix, childPrefix): 59 | isMac = runtimeHelpers.isMacintoshArch() 60 | 61 | s = '%s%s%s\n' % (prefix, '' if prefix == '' else ' ', _viewControllerDescription(vc)) 62 | 63 | nextPrefix = childPrefix + ' |' 64 | 65 | numChildViewControllers = fb.evaluateIntegerExpression('(int)[(id)[%s childViewControllers] count]' % (vc)) 66 | 67 | for i in range(0, numChildViewControllers): 68 | viewController = fb.evaluateExpression('(id)[(id)[%s childViewControllers] objectAtIndex:%d]' % (vc, i)) 69 | s += _recursiveViewControllerDescriptionWithPrefixAndChildPrefix(viewController, string, nextPrefix, nextPrefix) 70 | 71 | if not isMac: 72 | isModal = fb.evaluateBooleanExpression('%s != nil && ((id)[(id)[(id)%s presentedViewController] presentingViewController]) == %s' % (vc, vc, vc)) 73 | 74 | if isModal: 75 | modalVC = fb.evaluateObjectExpression('(id)[(id)%s presentedViewController]' % (vc)) 76 | s += _recursiveViewControllerDescriptionWithPrefixAndChildPrefix(modalVC, string, childPrefix + ' *M', nextPrefix) 77 | s += '\n// \'*M\' means the view controller is presented modally.' 78 | 79 | return string + s 80 | -------------------------------------------------------------------------------- /libexec/fblldbviewhelpers.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Copyright (c) 2014, Facebook, Inc. 4 | # All rights reserved. 5 | # 6 | # This source code is licensed under the BSD-style license found in the 7 | # LICENSE file in the root directory of this source tree. An additional grant 8 | # of patent rights can be found in the PATENTS file in the same directory. 9 | 10 | import lldb 11 | 12 | import fblldbbase as fb 13 | import fblldbobjcruntimehelpers as runtimeHelpers 14 | 15 | def flushCoreAnimationTransaction(): 16 | fb.evaluateEffect('[CATransaction flush]') 17 | 18 | def setViewHidden(object, hidden): 19 | fb.evaluateEffect('[{} setHidden:{}]'.format(object, int(hidden))) 20 | flushCoreAnimationTransaction() 21 | 22 | def maskView(viewOrLayer, color, alpha): 23 | unmaskView(viewOrLayer) 24 | window = fb.evaluateExpression('(UIWindow *)[[UIApplication sharedApplication] keyWindow]') 25 | origin = convertPoint(0, 0, viewOrLayer, window) 26 | size = fb.evaluateExpressionValue('(CGSize)((CGRect)[(id)%s frame]).size' % viewOrLayer) 27 | 28 | rectExpr = '(CGRect){{%s, %s}, {%s, %s}}' % (origin.GetChildMemberWithName('x').GetValue(), 29 | origin.GetChildMemberWithName('y').GetValue(), 30 | size.GetChildMemberWithName('width').GetValue(), 31 | size.GetChildMemberWithName('height').GetValue()) 32 | mask = fb.evaluateExpression('(id)[[UIView alloc] initWithFrame:%s]' % rectExpr) 33 | 34 | fb.evaluateEffect('[%s setTag:(NSInteger)%s]' % (mask, viewOrLayer)) 35 | fb.evaluateEffect('[%s setBackgroundColor:[UIColor %sColor]]' % (mask, color)) 36 | fb.evaluateEffect('[%s setAlpha:(CGFloat)%s]' % (mask, alpha)) 37 | fb.evaluateEffect('[%s addSubview:%s]' % (window, mask)) 38 | flushCoreAnimationTransaction() 39 | 40 | def unmaskView(viewOrLayer): 41 | window = fb.evaluateExpression('(UIWindow *)[[UIApplication sharedApplication] keyWindow]') 42 | mask = fb.evaluateExpression('(UIView *)[%s viewWithTag:(NSInteger)%s]' % (window, viewOrLayer)) 43 | fb.evaluateEffect('[%s removeFromSuperview]' % mask) 44 | flushCoreAnimationTransaction() 45 | 46 | def convertPoint(x, y, fromViewOrLayer, toViewOrLayer): 47 | fromLayer = convertToLayer(fromViewOrLayer) 48 | toLayer = convertToLayer(toViewOrLayer) 49 | return fb.evaluateExpressionValue('(CGPoint)[%s convertPoint:(CGPoint){ .x = %s, .y = %s } toLayer:(CALayer *)%s]' % (fromLayer, x, y, toLayer)) 50 | 51 | def convertToLayer(viewOrLayer): 52 | if fb.evaluateBooleanExpression('[(id)%s isKindOfClass:(Class)[CALayer class]]' % viewOrLayer): 53 | return viewOrLayer 54 | elif fb.evaluateBooleanExpression('[(id)%s respondsToSelector:(SEL)@selector(layer)]' % viewOrLayer): 55 | return fb.evaluateExpression('(CALayer *)[%s layer]' % viewOrLayer) 56 | else: 57 | raise Exception('Argument must be a CALayer, UIView, or NSView.') 58 | 59 | def isUIView(obj): 60 | return not runtimeHelpers.isMacintoshArch() and fb.evaluateBooleanExpression('[(id)%s isKindOfClass:(Class)[UIView class]]' % obj) 61 | 62 | def isNSView(obj): 63 | return runtimeHelpers.isMacintoshArch() and fb.evaluateBooleanExpression('[(id)%s isKindOfClass:(Class)[NSView class]]' % obj) 64 | 65 | def isView(obj): 66 | return isUIView(obj) or isNSView(obj) 67 | 68 | # Generates a BFS of the views tree starting at the given view as root. 69 | # Yields a tuple of the current view in the tree and its level (view, level) 70 | def subviewsOfView(view): 71 | views = [(view, 0)] 72 | yield views[0] 73 | while views: 74 | (view, level) = views.pop(0) 75 | subviews = fb.evaluateExpression('(id)[%s subviews]' % view) 76 | subviewsCount = int(fb.evaluateExpression('(int)[(id)%s count]' % subviews)) 77 | for i in xrange(subviewsCount): 78 | subview = fb.evaluateExpression('(id)[%s objectAtIndex:%i]' % (subviews, i)) 79 | views.append((subview, level+1)) 80 | yield (subview, level+1) 81 | 82 | def upwardsRecursiveDescription(view, maxDepth=0): 83 | if not fb.evaluateBooleanExpression('[(id)%s isKindOfClass:(Class)[UIView class]]' % view) and not fb.evaluateBooleanExpression('[(id)%s isKindOfClass:(Class)[NSView class]]' % view): 84 | return None 85 | 86 | currentView = view 87 | recursiveDescription = [] 88 | depth = 0 89 | 90 | while currentView and (maxDepth <= 0 or depth <= maxDepth): 91 | depth += 1 92 | 93 | viewDescription = fb.evaluateExpressionValue('(id)[%s debugDescription]' % (currentView)).GetObjectDescription() 94 | currentView = fb.evaluateExpression('(void*)[%s superview]' % (currentView)) 95 | try: 96 | if int(currentView, 0) == 0: 97 | currentView = None 98 | except: 99 | currentView = None 100 | 101 | if viewDescription: 102 | recursiveDescription.insert(0, viewDescription) 103 | 104 | if not len(viewDescription): 105 | return None 106 | 107 | currentPrefix = "" 108 | builder = "" 109 | for viewDescription in recursiveDescription: 110 | builder += currentPrefix + viewDescription + "\n" 111 | currentPrefix += " | " 112 | 113 | return builder 114 | 115 | def slowAnimation(speed=1): 116 | fb.evaluateEffect('[[[UIApplication sharedApplication] windows] setValue:@(%s) forKeyPath:@"layer.speed"]' % speed) 117 | --------------------------------------------------------------------------------