Fast Method Stubbing From LLDB


Bryce Pauken

 •  •  • 

Let’s say you’ve stumbled upon the following method:

+ (BOOL)isSecretFunctionalityEnabled {
    if (/* lots of important checks */) {
        return YES;
    }
    return NO;
}

Of course, you want that secret functionality — what’s the best way to enable it?

thread return

There’s a built-in lldb command that covers this case well: thread return.

(lldb) help thread return

Prematurely return from a stack
frame, short-circuiting execution
of newer frames and optionally
yielding a specified value.

When paused in the debugger, running thread return YES will essentially behave as if you had just encountered a return YES statement. This makes it a great candidate for stubbing out the above method — we can set a breakpoint at the beginning of the method, add our debugger command, and even tell lldb to automatically continue execution after the breakpoint was triggered.

A thread return-ing breakpoint in Xcode

This works great almost all of the time, but it does have at least two notable drawbacks:

  1. It only supports scalar & floating-point values. No returning CGRect, for example.
  2. It’s slow. Like, slow slow.

Compared to a normal method call, an automatically-continuing breakpoint is less performant by a huge factor:

thread return:       160 ms
normal method call:  0.00002 ms

If the method you’re trying to stub out is only called once, or at least called infrequently, this is perfectly fine. But if your method is called in a tight loop or in any other time-sensitive section, this simple breakpoint can bring your app to a crawl.

Do we have any other options?

Swizzling from lldb

There’s an Objective-C method whose implementation we want to replace — sounds like a call for method swizzling!

Normally, we would define a new method in code that we could then use as our replacement when swizzling. But in my case, I want to do everything from lldb if possible, especially since I’d like to use this while debugging other processes.

We need to create a new function implementation at runtime that returns the appropriate value — blocks are an easy way to do that. Let’s create a new block from lldb, and note its address:

(lldb) e ^BOOL() { return YES; }
(BOOL (^)()) $0 = 0x000000010539f4d0

Note: To my knowledge, blocks should work fine in every case; but if not, we can also fall back to creating a regular c function from lldb using expression’s --top-level flag:

(lldb) e --top-level -- BOOL replacementImp() { return 1; }
(lldb) p/x replacementImp
    (BOOL (*)()) $1 = 0x000000011091d110

Now that we have a new function implementation, we can switch to using it. Swizzling is commonly done via a call to method_exchangeImplementations, but that requires our implementation to first be added to a class.

We could work around this (either by adding our implementation to an existing class, or even by creating a new class at runtime), but given our simple use case, we can use method_setImplementation instead to make things a bit easier — it requires only a pointer to the replacement implementation.

(lldb) expression
    Enter expressions, then terminate
    with an empty line to evaluate:

method_setImplementation(
  class_getInstanceMethod(
    [SomeClass class],
    @selector(isSecretFunctionalityEnabled)
  ),
  (IMP)0x000000011091d110
)

(IMP) $2 = 0x00000001024054b0

Casting a block straight to an IMP here is not strictly a good idea — for one, the parameters the block is expecting are different than what a normal Objective-C method will expect. That’s not an issue here, but if you need it, imp_implementationWithBlock should help fix the parameters.

Looks like the expression executed fine, so let’s try it out:

(lldb) p [SomeClass isSecretFunctionalityEnabled]
(BOOL) $3 = YES

It works! Our method now returns a new value, all from lldb.

While it does require a bit of effort, this configuration does give us some benefits:

  1. No performance penalty after the initial setup
  2. Supports any return type, not just scalars & floats
  3. Easily scriptable!

Other options

This setup is the best one I’ve found when depending only on lldb. However, it’s worth calling out some other options if you’re willing to pull in dependencies or want to handle this from within the app itself — Aspects is a great one, and OCMock should cover most cases as well.

Both use message forwarding at their cores, which is still a bit slower than a normal method call, but plenty fast for almost all cases.

Finally, due to a malformed experiment early on (after which I thought using blocks as replacement implementations was not a viable option), there was an earlier version of this post that relied on writing machine code into memory — creating an architecture-specific implementation of a function at runtime to return a given value. While among one of the hackiest bits of code I’ve written (at least, in recent memory…), I’ll maintain that it’s still technically an option!


Say Hello!