How improve your skills in lldb debugging

Radoslaw Cieciwa
4 min readJul 21, 2016

--

After reading this you should know:

  1. How to use expression command
  2. How to enter debugger without breakpoints
  3. How to assign and use variables in lldb
  4. What are, why you need and how to catch debug frames

TL;DR;

So, let’s get to the story…

Sometimes in your life, you just sit and ask yourself, what will happen next. And there’s sometimes awkward moment when nothing happens. In case of applications it’s normally a bug that causes this. Same was in our example:

Initializing application screen

Our app randomly just stayed in this lethal state. Why? At Brainly we have quite complicated application booting process:

  • we load our A/B test definitions
  • we let user pick their country
  • request for their country ”educational” configuration

Each step is asyncronous and have to be done in proper order. Each one of them is represented by NSOperation and all of them are ordered nicely in NSOperationQueue. To order them we use dependencies on NSOperation. Actually we have a code developed on those classes provided by Apple in WWDC: Advanced NSOperations. As you may already assume, problem lies in concurency: one of the steps NSOperation) is probably hanging there.

Let’s dig into some essential commands:

Expression command can be used to just change stuff, assign stuff, also with a multiline parts of code. Double hyphen in the example is there to distinc proper language (swift or objc) expression from params.

(lldb) expression [params] -- [command]
(lldb) ex -O -- self

Where this example is equivalent to:

(lldb) po self

How to get to our objects graph without a breakpoints

Without a breakpoints on running app, we have to just pause the execution — do a trap.

Tap pasue button to stop the app
This is how trapped code should look like

After making a trap, let’s dig into lldb console (xCode bottom segment). In our example we have:

(lldb) e id $delegate = (AppDelegate *)[[UIApplication sharedApplication] delegate]
(lldb) po $delegate
<AppDelegate: 0x7feb82c0f600>

Below is our AppDelegate code, OperationQueue is the one responsible for keeping Operations in order.

@interface AppDelegate ()<AppReloadProtocol>
@property(nonatomic, readwrite) OperationQueue *operationQueue;

Let’s check our operation queue object and it’s operations.

(lldb) po [$delegate operationQueue]
<Brainly.OperationQueue: 0x7feb82e7c290>{name = ‘NSOperationQueue 0x7feb82e7c290’}
(lldb) po [[$delegate operationQueue] operations]
<__NSArrayM 0x7feb82f63f30>(
<Brainly.LoadAppOperation: 0x7feb82f66cb0>{name = ‘LoadAppOperation’},
<NSBlockOperation: 0x7feb82f71690>
)

LoadAppOperation is actually the one of interest for us. Second one is just a handler for completion

(lldb) e id $load = (LoadAppOperation *)[[[$delegate operationQueue] operations] objectAtIndex:0](lldb) po $load
<Brainly.LoadAppOperation: 0x7feb82f66cb0>{name = ‘LoadAppOperation’}

And again LoadAppOperation class has a operationQueue.

(lldb) e id $ops = (NSArray *)[[$load operationQueue] operations](lldb) e id $go = $ops[0]
(lldb) po $go
<Brainly.GroupOperation: 0x7feb82c35de0>

So inside this one, we’ve get to a point of Apple class GroupOperation,

class GroupOperation: Operation {
private let internalQueue = OperationQueue()

It has internalQueue of type NSOperationQueue. Let’s try to get it:

(lldb) po $go.internalQueue
error: property ‘internalQueue’ not found on object of type ‘id’
error: 1 errors parsing expression

And we cannot debug Swift private properties!!!

I’ve talked about this with Apple engineer and we came to conclusion that it should, so this is a lldb bug.

How to get Swift private property?

We have a solution for that, a FRAME.

So what’s a frame:

It’s a data associated with function call, consisting from:

  • function address
  • function arguments
  • function local variables

It is a very source of variables that you have in your debugger console.

Let’s get back to story, after trapping the app with pause button, we don’t have any call stack we can read frames from. To catch a frame here:

  1. Set a breakpoint in one of the methods
  2. Invoke that method on object that you want to debug
(lldb) breakpoint set -f GroupOperation.swift — line 53
Breakpoint 3: where = Brainly`Brainly.GroupOperation.execute () -> () + 12 at GroupOperation.swift:53, address = 0x000000010c8a4b8c
(lldb) ex -i false — (void)[$go execute]
error: Execution was interrupted, reason: breakpoint 3.1.
The process has been left at the point where it was interrupted, use “thread return -x” to return to the state before expression evaluation.

This command:

ex -i false — (void)[$go execute]

Stands for (i = false) don’t ignore breakpoints ahead. And run execute function on $go variable, that don’t return nothing — it’s void. This function threw us to GroupOperation object and waiting breakpoint. And now we have a call stack:

(lldb) bt
* thread #1: tid = 0x3e341b, 0x000000010a10a24c (...)
* frame #0: 0x000000010a10a24c Brainly`GroupOperation.execute (...)
frame #1: 0x000000010a10a302 Brainly`@objc GroupOperation. (...)

So now we can check our GroupOperation for his private properties:

(lldb) frame variable self -D 1
(Brainly.GroupOperation) self = 0x00007fca7a68ae30 {
Brainly.Operation ={…}
internalQueue = 0x00007fca7a6895f0{…}
startingOperation = 0x00007fca7a68ac40{…}
finishingOperation = 0x00007fca7a68b040{…}
aggregatedErrors = 0 values{…}
}

Frame command allows us to print information about all public and private propertie of object.

frame variable is a recursive command, it prints the same informations for properties. -D 1 narrows the depth of this recursion to 1 (please try without it on your debug session).

Assign variable by address

(lldb) e -l objc — id $queue = (id)0x00007fca7a6895f0
(lldb) po $queue
<Brainly.OperationQueue: 0x7fd9926d9c40>{name = 'NSOperationQueue 0x7fd9926d9c40'}
(lldb) e -l objc -O — (id)[$queue operations]
<__NSArrayM 0x7fca7a6a05b0>(
<NSBlockOperation: 0x7fca7a68b040>,
<Brainly.LoadConfigOperation: 0x7fca7a59e2c0>{name = ‘LoadConfigOperation’}
)

First NSBlockOperation is a completion callback, so clearly we have only LoadConfigOperation running, let’s confirm that.

(lldb) ex -l objc -O — id $loadConfigOperation = (id)0x7fd99256f910
(lldb) ex -l objc — (BOOL)[$loadConfigOperation isFinished]
(BOOL) $2 = NO

Ok, long story short, after fixing the concurrency bug in LoadConfigOperation we solved our problem.

--

--

Radoslaw Cieciwa

Father, passionate Instagrammer, sportsman and a very crafty iOS Developer