Skip to content

Commit

Permalink
Merge pull request #216 from facebook/findinstances-command
Browse files Browse the repository at this point in the history
Implement findinstances command
  • Loading branch information
kastiglione authored Jan 4, 2018
2 parents 8525cbd + 768c33f commit 0740627
Show file tree
Hide file tree
Showing 2 changed files with 120 additions and 0 deletions.
108 changes: 108 additions & 0 deletions commands/FBDebugCommands.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import fblldbbase as fb
import fblldbobjcruntimehelpers as objc

import sys
import os
import re

def lldbcommands():
Expand All @@ -12,6 +14,7 @@ def lldbcommands():
FBFrameworkAddressBreakpointCommand(),
FBMethodBreakpointCommand(),
FBMemoryWarningCommand(),
FBFindInstancesCommand(),
]

class FBWatchInstanceVariableCommand(fb.FBCommand):
Expand Down Expand Up @@ -184,3 +187,108 @@ def description(self):

def run(self, arguments, options):
fb.evaluateEffect('[[UIApplication sharedApplication] performSelector:@selector(_performMemoryWarning)]')


class FBFindInstancesCommand(fb.FBCommand):
def name(self):
return 'findinstances'

def description(self):
return """
Find instances of specified ObjC classes.
This command scans memory and uses heuristics to identify instances of
Objective-C classes. This includes Swift classes that descend from NSObject.
Basic examples:
findinstances UIScrollView
findinstances *UIScrollView
findinstances UIScrollViewDelegate
These basic searches find instances of the given class or protocol. By
default, subclasses of the class or protocol are included in the results. To
find exact class instances, add a `*` prefix, for example: *UIScrollView.
Advanced examples:
# Find views that are either: hidden, invisible, or not in a window
findinstances UIView hidden == true || alpha == 0 || window == nil
# Find views that have either a zero width or zero height
findinstances UIView layer.bounds.#size.width == 0 || layer.bounds.#size.height == 0
# Find leaf views that have no subviews
findinstances UIView subviews.@count == 0
# Find dictionaries that have keys that might be passwords or passphrases
findinstances NSDictionary any @allKeys beginswith 'pass'
These examples make use of a filter. The filter is implemented with
NSPredicate, see its documentaiton for more details. Basic NSPredicate
expressions have relatively predicatable syntax. There are some exceptions
as seen above, see https://github.com/facebook/chisel/wiki/findinstances.
"""

def run(self, arguments, options):
if not self.loadChiselIfNecessary():
return

if len(arguments) == 0 or not arguments[0].strip():
print 'Usage: findinstances <classOrProtocol> [<predicate>]; Run `help findinstances`'
return

# Unpack the arguments by hand. The input is entirely in arguments[0].
args = arguments[0].strip().split(' ', 1)

query = args[0]
if len(args) > 1:
predicate = args[1].strip()
# Escape double quotes and backslashes.
predicate = re.sub('([\\"])', r'\\\1', predicate)
else:
predicate = ''
call = '(void)PrintInstances("{}", "{}")'.format(query, predicate)
fb.evaluateExpressionValue(call)

def loadChiselIfNecessary(self):
target = lldb.debugger.GetSelectedTarget()
if target.module['Chisel']:
return True

path = self.chiselLibraryPath()
if not os.path.exists(path):
print 'Chisel library missing: ' + path
return False

module = fb.evaluateExpressionValue('(void*)dlopen("{}", 2)'.format(path))
if module.unsigned != 0 or target.module['Chisel']:
return True

# `errno` is a macro that expands to a call to __error(). In development,
# lldb was not getting a correct value for `errno`, so `__error()` is used.
errno = fb.evaluateExpressionValue('*(int*)__error()').value
error = fb.evaluateExpressionValue('(char*)dlerror()')
if errno == 50:
# KERN_CODESIGN_ERROR from <mach/kern_return.h>
print 'Error loading Chisel: Code signing failure; Must re-run codesign'
elif error.unsigned != 0:
print 'Error loading Chisel: ' + error.summary
elif errno != 0:
error = fb.evaluateExpressionValue('(char*)strerror({})'.format(errno))
if error.unsigned != 0:
print 'Error loading Chisel: ' + error.summary
else:
print 'Error loading Chisel (errno {})'.format(errno)
else:
print 'Unknown error loading Chisel'

return False

def chiselLibraryPath(self):
# script os.environ['CHISEL_LIBRARY_PATH'] = '/path/to/custom/Chisel'
path = os.getenv('CHISEL_LIBRARY_PATH')
if path and os.path.exists(path):
return path

source_path = sys.modules[__name__].__file__
source_dir = os.path.dirname(source_path)
# ugh: ../.. is to back out of commands/, then back out of libexec/
return os.path.join(source_dir, '..', '..', 'lib', 'Chisel.framework', 'Chisel')
12 changes: 12 additions & 0 deletions fblldbbase.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,19 @@ def evaluateExpressionValue(expression, printErrors=True, language=lldb.eLanguag
frame = lldb.debugger.GetSelectedTarget().GetProcess().GetSelectedThread().GetSelectedFrame()
options = lldb.SBExpressionOptions()
options.SetLanguage(language)

# Allow evaluation that contains a @throw/@catch.
# By default, ObjC @throw will cause evaluation to be aborted. At the time
# of a @throw, it's not known if the exception will be handled by a @catch.
# An exception that's caught, should not cause evaluation to fail.
options.SetTrapExceptions(False)

# Give evaluation more time.
options.SetTimeoutInMicroSeconds(5000000) # 5s

# Chisel commands are not multithreaded.
options.SetTryAllThreads(False)

value = frame.EvaluateExpression(expression, options)
error = value.GetError()

Expand Down

0 comments on commit 0740627

Please sign in to comment.