4
4
import fblldbbase as fb
5
5
import fblldbobjcruntimehelpers as objc
6
6
7
+ import sys
8
+ import os
7
9
import re
8
10
9
11
def lldbcommands ():
@@ -12,6 +14,7 @@ def lldbcommands():
12
14
FBFrameworkAddressBreakpointCommand (),
13
15
FBMethodBreakpointCommand (),
14
16
FBMemoryWarningCommand (),
17
+ FBFindInstancesCommand (),
15
18
]
16
19
17
20
class FBWatchInstanceVariableCommand (fb .FBCommand ):
@@ -184,3 +187,108 @@ def description(self):
184
187
185
188
def run (self , arguments , options ):
186
189
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