Takeaways from Advanced Debugging with Xcode and LLDB

I found this talk from WWDC 2018 very enlightening and wanted to capture notes from it easier reference. The existence of this document should not preclude one from watching this fantastic presentation!

LLDB Magic

A key theme from this talk is how to avoid changing code (and thus recompiling) for debugging purposes or testing changes. This is done primarily with breakpoints that automatically continue after running their actions in conjunction with the expression lldb command (and various aliases thereof).

expression executes an arbitrary target language expression in the context of the current stack frame and thread. help expression in lldb gives more, and more precise, information.

For instance, to always set a variable to a particular value, regardless of input

  1. Create a breakpoint at a place where you need the variable to be that value
  2. Edit the breakpoint and add an action of expression [variable] = [value]
  3. Check Automatically continue after evaluating actions

This general technique can also be used to try out code changes without recompiling. If you want to replace code first create a breakpoint, add the expression actions with the new code, and add an action to bump the instruction pointer past the old code (thread jump --by n, where n is the number of lines of code) 1.

Symbolic breakpoints

When a symbolic breakpoint is created it should have at least one child breakpoint -- child breakpoints show that the debugger found (a) matching symbol(s) in the program. No child breakpoints means the debugger found no matching symbols and will never be triggered.

When a symbolic breakpoint is triggered you can inspect the arguments passed in by inspecting the appropriate registers, which depends on your target architecture 2. More simply though, if you are in an objective-c method you can use lldb's built-in pseudo-registers: $arg1, $arg2, $arg3,... $argn. (e.g., poc $arg1 prints out the object the message was passed to)

In Objective-C, the first argument ($arg1) is the object the message is being sent to, the second ($arg2) is the selector (you may need to cast it as type SEL), and the rest are the method/function parameters.

Temporary breakpoints

Sometimes you want to hit a breakpoint based on some condition or action taken 3, but for some reason you can't use the 'Conditon' field in the breakpoint editor 4. You can set temporary breakpoints using an action. The --one-shot true (or -o true) argument to the breakpoint command will create a breakpoint that is removed when it's hit.

Object descriptions

Overview of lldb print commands:

expression -l objc -O -- <objective-c expression> will evaluate the expression in the context of objective-c, which can be necessary doing something more dynamic than swift allows. This pattern is fairly useful but onerous to type every time, so it can be aliased to, say, poc with command alias poc expresson -l objc -O --

poc can be used to dereference a pointer address and print the description of the object at that address.

A way to do almost the same thing in Swift is to use unsafeBitCast like so: po unsafeBitCast(0x1234abcde, to: ClassName.self).

Unlike the raw address in poc 0x1234abcde, unsafeBitCast(0x1234abcde, to: ClassName.self) returns a typed result, so the properties of the object at 0x1234abcde can be inspected. In the demo, the object is a ScoreboardView so you can do things like po unsafeBitCast(0x7fb3[...], to: ScoreboardView.self).center to get the value of the the center CGPoint or po unsafeBitCast(0x7fb3[...], to: ScoreboardView.self).center.y = 300 to move the center of the view.

View debugging

After you move views around, you can update the screen (while the app is paused!) by flushing the current CATransaction with expression CATransaction.flush() -- this is a handy command, so it's a good idea to alias it.

Check out the "nudge.py" lldb command, downloadable from https://developer.apple.com/sample-code/wwdc/2018/UseScriptsToAddCustomCommandsToLLDB.zip Adding this to your lldb session will allow you to "nudge" views around while execution is paused and see the result immediately.

Malloc stack logging - set to "All Allocation and Free History". This will allow you to see where any allocated instance in either the view debugger or memory graph was allocated using the "Backtrace" inspector.

⌘-click allows you to click "through" views in the view debugger, in case you want to click a view that is not highest in the z-plane.

In addition to using the view debugger to find a particular view, you can call -[UIView recursiveDescription] on a view -- you have to use the objective-c message passing syntax, not the swift method call. This is because recursiveDescription is for debugging only and not part of the public UIView API which means it isn't passed through to swift. You can call using objective-c by using the poc alias mentioned previously, like so: poc [`self.view` recursiveDescription] The backticks around self.view are there because evaluating an expression in objective-c creates a new context which doesn't contain all of our variables. The backticks tell expression to evaluate whatever is between them in the current context and replace them with the result.

Misc

Further Reading

Some more Readings in Advanced LLDB:

Objc.io - Dancing in the Debugger — A Waltz with LLDB

Radoslaw Cieciwa - How improve your skills in lldb debugging

Sozin's Comet (SO comment) - Setting a register value to NSString in lldb

Daniel Martin - How to Extend LLDB to Provide a Better Debugging Experience

The LLDB project doesn't have a comprehensive list of lldb commands (the documentation in general is fairly poor) but this map of GDB to LLDB commands gives an overview of some of them.


  1. If you just want to move the instruction pointer once, grab its handle in the IDE (it's the green label on right of the editor when paused at a breakpoint) and drag it.

  2. Ray Wenderlich has a tutorial covering register calling conventions.

  3. For instance, you have a label that can be updated due to several things happening but you only want to breakpoint when one of those several ways occurs. You can create a breakpoint with a breakpoint set -o true -f <file name> -l <line number> action that is hit when the thing occurs, where <file name> and <line number> correspond to the code what updates the label.

  4. Perhaps the variable isn't in scope or there is no variable associated with the action triggering the breakpoint.