Skip to content

Commit 0740627

Browse files
authored
Merge pull request #216 from facebook/findinstances-command
Implement findinstances command
2 parents 8525cbd + 768c33f commit 0740627

File tree

2 files changed

+120
-0
lines changed

2 files changed

+120
-0
lines changed

commands/FBDebugCommands.py

+108
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
import fblldbbase as fb
55
import fblldbobjcruntimehelpers as objc
66

7+
import sys
8+
import os
79
import re
810

911
def lldbcommands():
@@ -12,6 +14,7 @@ def lldbcommands():
1214
FBFrameworkAddressBreakpointCommand(),
1315
FBMethodBreakpointCommand(),
1416
FBMemoryWarningCommand(),
17+
FBFindInstancesCommand(),
1518
]
1619

1720
class 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')

fblldbbase.py

+12
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,19 @@ def evaluateExpressionValue(expression, printErrors=True, language=lldb.eLanguag
4242
frame = lldb.debugger.GetSelectedTarget().GetProcess().GetSelectedThread().GetSelectedFrame()
4343
options = lldb.SBExpressionOptions()
4444
options.SetLanguage(language)
45+
46+
# Allow evaluation that contains a @throw/@catch.
47+
# By default, ObjC @throw will cause evaluation to be aborted. At the time
48+
# of a @throw, it's not known if the exception will be handled by a @catch.
49+
# An exception that's caught, should not cause evaluation to fail.
4550
options.SetTrapExceptions(False)
51+
52+
# Give evaluation more time.
53+
options.SetTimeoutInMicroSeconds(5000000) # 5s
54+
55+
# Chisel commands are not multithreaded.
56+
options.SetTryAllThreads(False)
57+
4658
value = frame.EvaluateExpression(expression, options)
4759
error = value.GetError()
4860

0 commit comments

Comments
 (0)