Fast Method Stubbing From LLDB
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.
This works great almost all of the time, but it does have at least two notable drawbacks:
- It only supports scalar & floating-point values. No returning
CGRect
, for example. - 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 fromlldb
usingexpression
’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:
- No performance penalty after the initial setup
- Supports any return type, not just scalars & floats
- 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!