44import fblldbbase as fb
55import fblldbobjcruntimehelpers as objc
66
7+ import sys
8+ import os
79import re
810
911def lldbcommands ():
@@ -12,6 +14,7 @@ def lldbcommands():
1214 FBFrameworkAddressBreakpointCommand (),
1315 FBMethodBreakpointCommand (),
1416 FBMemoryWarningCommand (),
17+ FBFindInstancesCommand (),
1518 ]
1619
1720class FBWatchInstanceVariableCommand (fb .FBCommand ):
@@ -184,3 +187,108 @@ def description(self):
184187
185188 def run (self , arguments , options ):
186189 fb .evaluateEffect ('[[UIApplication sharedApplication] performSelector:@selector(_performMemoryWarning)]' )
190+
191+
192+ class FBFindInstancesCommand (fb .FBCommand ):
193+ def name (self ):
194+ return 'findinstances'
195+
196+ def description (self ):
197+ return """
198+ Find instances of specified ObjC classes.
199+
200+ This command scans memory and uses heuristics to identify instances of
201+ Objective-C classes. This includes Swift classes that descend from NSObject.
202+
203+ Basic examples:
204+
205+ findinstances UIScrollView
206+ findinstances *UIScrollView
207+ findinstances UIScrollViewDelegate
208+
209+ These basic searches find instances of the given class or protocol. By
210+ default, subclasses of the class or protocol are included in the results. To
211+ find exact class instances, add a `*` prefix, for example: *UIScrollView.
212+
213+ Advanced examples:
214+
215+ # Find views that are either: hidden, invisible, or not in a window
216+ findinstances UIView hidden == true || alpha == 0 || window == nil
217+ # Find views that have either a zero width or zero height
218+ findinstances UIView layer.bounds.#size.width == 0 || layer.bounds.#size.height == 0
219+ # Find leaf views that have no subviews
220+ findinstances UIView subviews.@count == 0
221+ # Find dictionaries that have keys that might be passwords or passphrases
222+ findinstances NSDictionary any @allKeys beginswith 'pass'
223+
224+ These examples make use of a filter. The filter is implemented with
225+ NSPredicate, see its documentaiton for more details. Basic NSPredicate
226+ expressions have relatively predicatable syntax. There are some exceptions
227+ as seen above, see https://github.com/facebook/chisel/wiki/findinstances.
228+ """
229+
230+ def run (self , arguments , options ):
231+ if not self .loadChiselIfNecessary ():
232+ return
233+
234+ if len (arguments ) == 0 or not arguments [0 ].strip ():
235+ print 'Usage: findinstances <classOrProtocol> [<predicate>]; Run `help findinstances`'
236+ return
237+
238+ # Unpack the arguments by hand. The input is entirely in arguments[0].
239+ args = arguments [0 ].strip ().split (' ' , 1 )
240+
241+ query = args [0 ]
242+ if len (args ) > 1 :
243+ predicate = args [1 ].strip ()
244+ # Escape double quotes and backslashes.
245+ predicate = re .sub ('([\\ "])' , r'\\\1' , predicate )
246+ else :
247+ predicate = ''
248+ call = '(void)PrintInstances("{}", "{}")' .format (query , predicate )
249+ fb .evaluateExpressionValue (call )
250+
251+ def loadChiselIfNecessary (self ):
252+ target = lldb .debugger .GetSelectedTarget ()
253+ if target .module ['Chisel' ]:
254+ return True
255+
256+ path = self .chiselLibraryPath ()
257+ if not os .path .exists (path ):
258+ print 'Chisel library missing: ' + path
259+ return False
260+
261+ module = fb .evaluateExpressionValue ('(void*)dlopen("{}", 2)' .format (path ))
262+ if module .unsigned != 0 or target .module ['Chisel' ]:
263+ return True
264+
265+ # `errno` is a macro that expands to a call to __error(). In development,
266+ # lldb was not getting a correct value for `errno`, so `__error()` is used.
267+ errno = fb .evaluateExpressionValue ('*(int*)__error()' ).value
268+ error = fb .evaluateExpressionValue ('(char*)dlerror()' )
269+ if errno == 50 :
270+ # KERN_CODESIGN_ERROR from <mach/kern_return.h>
271+ print 'Error loading Chisel: Code signing failure; Must re-run codesign'
272+ elif error .unsigned != 0 :
273+ print 'Error loading Chisel: ' + error .summary
274+ elif errno != 0 :
275+ error = fb .evaluateExpressionValue ('(char*)strerror({})' .format (errno ))
276+ if error .unsigned != 0 :
277+ print 'Error loading Chisel: ' + error .summary
278+ else :
279+ print 'Error loading Chisel (errno {})' .format (errno )
280+ else :
281+ print 'Unknown error loading Chisel'
282+
283+ return False
284+
285+ def chiselLibraryPath (self ):
286+ # script os.environ['CHISEL_LIBRARY_PATH'] = '/path/to/custom/Chisel'
287+ path = os .getenv ('CHISEL_LIBRARY_PATH' )
288+ if path and os .path .exists (path ):
289+ return path
290+
291+ source_path = sys .modules [__name__ ].__file__
292+ source_dir = os .path .dirname (source_path )
293+ # ugh: ../.. is to back out of commands/, then back out of libexec/
294+ return os .path .join (source_dir , '..' , '..' , 'lib' , 'Chisel.framework' , 'Chisel' )
0 commit comments