Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add findinstances, and new support framework in Chisel.xcodeproj #197

Merged
merged 1 commit into from
Nov 17, 2017

Conversation

kastiglione
Copy link
Contributor

@kastiglione kastiglione commented Jul 23, 2017

This adds a new command, findinstances, which is implemented mostly in native code and so comes with Chisel.xcodeproj which contains a new Chisel framework. Future Chisel commands can implement the heavy lifing this way too.

The findinstances command scans the heap using available iOS/macOS malloc API. For each allocation, a number of heuristics are peformed to identify likely Objective-C instances. The heuristics do not call methods on the objects, instead relying only on objc runtime functions to passively match the instance based on its class metadata. This avoids allocations and stateful side effects in the objc runtime.

After this first pass, the candidate objects go through a second pass that checks if they match against an optional NSPredicate. If there's no predicate, the object is printed with minimal information. If there is a predicate, and the object passes the predicate, then the object will be printed out with more detail, specifically the detail queried in the predicate.

Some examples of findinstances:

findinstances UIView
findinstances *UIView
findinstances UIScrollViewDelegate
findinstances UIView window == nil || hidden == true || alpha == 0 || layer.bounds.#size.width == 0 ||  layer.bounds.#size.height == 0 
findinstances UIView subviews.@count == 0
findinstances NSDictionary any @allKeys beginswith 'perf_'
findinstances NSArray @count > 100

This adds a new command, `findinstances`, which is implemented mostly in native
code and so comes with Chisel.xcodeproj which contains a new Chisel framework.
Future Chisel commands can implement the heavy lifing this way too.

The `findinstances` command scans the heap using available iOS/macOS malloc API.
For each allocation, a number of heuristics are peformed to identify likely
Objective-C instances. The heuristics do not call methods on the objects,
instead relying only on objc runtime functions to passively match the instance
based on its class metadata. This avoids allocations and stateful side effects
in the objc runtime.

After this first pass, the candidate objects go through a second pass that
checks if they match against an optional `NSPredicate`. If there's no predicate,
the object is printed with minimal information. If there is a predicate, and the
object passes the predicate, then the object will be printed out with more
detail, specifically the detail queried in the predicate.
@kastiglione
Copy link
Contributor Author

kastiglione commented Jul 23, 2017

⚠️ Update ⚠️ The below comment is now outdated and unnecessary, don't follow the instructions. The findinstances command was added in #216 and is available in Chisel 1.7.0.


For anyone wanting to try this at home, build the Xcode project, and get the path to the Chisel framework. On my machine it's at:

/Users/<me>/Library/Developer/Xcode/DerivedData/Chisel-<stuff>/Build/Products/Debug-iphonesimulator/Chisel.framework/Chisel

Then, in lldb do:

expr -l objc -- (void*)dlopen("/path/to/Chisel.framework/Chisel", 2)

Then to call it, run:

script o=lldb.SBExpressionOptions(); o.SetLanguage(lldb.eLanguageTypeObjC); o.SetTrapExceptions(False); o.SetTryAllThreads(False); o.SetTimeoutInMicroSeconds(10*1000000); lldb.frame.EvaluateExpression('(void)PrintInstances("<classname>", "<predicate>")', o)

Where <classname> can be either a class name or a protocol name, and <predicate> is a string that can be parsed by NSPredicate.

This could be made easier using regex command:

(note the line break after findinstances is intentional)

command regex findinstances
s/(\S+) *(.*)/script o=lldb.SBExpressionOptions(); o.SetLanguage(lldb.eLanguageTypeObjC); o.SetTrapExceptions(False); o.SetTryAllThreads(False); o.SetTimeoutInMicroSeconds(10*1000000); lldb.frame.EvaluateExpression('(void)PrintInstances("%1", "%2")', o)/

Or, as a python command, stored in path/to/findinstances.py:

import lldb

def findinstances(debugger, command, exe_ctx, result, _):
    options = lldb.SBExpressionOptions()
    options.SetTrapExceptions(False)
    options.SetTryAllThreads(False)
    options.SetTimeoutInMicroSeconds(10*1000000)
    options.SetLanguage(lldb.eLanguageTypeObjC)

    frame = exe_ctx.frame

    if not exe_ctx.target.module['Chisel']:
        frame.EvaluateExpression('(void*)dlopen("/path/to/Chisel.framework/Chisel", 2)', options)

    args = command.split(' ', 1)
    typeName = args[0]
    predicate = args[1] if len(args) > 1 else ''
    frame.EvaluateExpression('(void)PrintInstances("{}", "{}")'.format(typeName, predicate), options)

def __lldb_init_module(debugger, _):
    debugger.HandleCommand('command script add -f findinstances.findinstances findinstances')

@kastiglione kastiglione merged commit 2bc1bf6 into master Nov 17, 2017
@kastiglione
Copy link
Contributor Author

I'll be doing follow up pull requests.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants