Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Save more on your purchases now! discount-offer-chevron-icon
Savings automatically calculated. No voucher code required.
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Conferences
Free Learning
Arrow right icon

LLDB and the Command Line

Save for later
  • 23 min read
  • 15 Feb 2017

article-image

In this article by Stuart Grimshaw, authors of the book Mastering macOS Programming, we're going to shift up a gear and reach deep into the LLDB environment, both in Xcode and as a standalone process. Some of this stuff may look a little esoteric at first glance, but rest assured, it is anything but.

Working professionally as a developer means being comfortable with the command line, being aware of what is available in LLDB, and having at least a working knowledge of the scripting languages that are a part of developing software on almost all platforms.

It's also really, really cool. Once you have a bit of momentum behind you, you'll wonder how you ever lived without these tools, which allow you to automate and customize and get right into the inner workings of your code.

(For more resources related to this topic, see here.)

In this article, you will learn about the following:

  • What LLDB does
  • Running code from within LLDB
  • Creating and manipulating breakpoints and watchpoints
  • How to customize LLDB to your requirements
  • How to run the Swift REPL from within LLDB
  • How to run a Python REPL within LLDB

This one will require a lot of hands-on experimentation to familiarize yourself with what is presented here. So, take this one slowly, and try out as much as you can.

Once you have absorbed this stuff, you'll never be the same developer again.

LLDB

So, what is this LLDB? Why do we see it at the console whenever we hit a breakpoint (or a bug)? This is the LLDB prompt:

(lldb)

Well, LLDB stands for low level debugger, and it does what it says it does. It actually does a bit more than that, which we'll get to very soon. LLDB makes use of several components from the LLVM project, which is basically a compiler, parser, and loads of other stuff that weneed (or Xcode needs) to build a program. However, as interesting as that is to look at, it is out of the scope of this article, and we will focus entirely on the debugger in this article.

LLDB is not only available in the debug area console of Xcode, where we have made frequent use of it already,it can also be used at the command line, which we will also cover in this article.

The LLDB interface is a very powerful tool; you can print to it from your code, you can interact with it, configure it to your requirements, change settings on the fly, and access both the Swift REPL and the Unix system that underlies Mac OS, without having to terminate a session.

Using LLDB

Returning to the code, set a breakpoint at this line of code:

printObservation()

Once execution has stopped at that breakpoint, type the following into the console:

(lldb)bt

This will produce a list of all the stack frames on the current thread that lead up to the line of code where execution was halted by the breakpoint, bt stands for backtrace. The output will look like this:

(lldb) bt
* thread #1: tid = 0x105a7d, 0x000000010000177c 5623_16_code`ViewController.testStepCommands(self=0x00006080000c2220) -> () + 12 at ViewController.swift:62, queue = 'com.apple.main-thread', stop reason = breakpoint 3.1
  * frame #0: 0x000000010000177c 5623_16_code`ViewController.testStepCommands(self=0x00006080000c2220) -> () + 12 at ViewController.swift:62
    frame #1: 0x00000001000014b2 5623_16_code`ViewController.viewDidLoad(self=0x00006080000c2220) -> () + 98 at ViewController.swift:17

... lots more edited out

    frame #16: 0x00007fffbfafc255 libdyld.dylib`start + 1
(lldb)

There is no need to understand all of this immediately,but looking through it, you will probably recognize the symbols at the top of the stack, since they are the methods that you have coded yourself. Everything leading up to that is provided by the frameworks you use. In this case, we see that most of the work is being done by AppKit, with a modest contribution from our own code (sorry, the truth can hurt at times).

Frame0 is the last frame that was put on the stack. Above that, we see some information about the current thread, its number and thread ID (tid) and so on, as well as the line of code at which execution has been halted, and even the reason for doing so. In this case, we have hit breakpoint 3.1. Remember the viewDidLoad symbolic breakpoint produced several breakpoints? That's what the number behind the decimal point means, and breakpoint3 only occurs once, hence there can only be a 3.1.

In large projects, this is an excellent way to answer the perennial question, how did the code get here?

Debugging line-by-line

Once we have halted the program at a breakpoint, we can take over control of its further execution:

Continue

The following commands are available to continue execution of the code (up until any subsequent breakpoints):

(lldb) continue
(lldb) c
(lldb) thread continue

They are all equivalent.

Step over

The following commands are equivalent to clicking on the step-over button:

(lldb) next 
(lldb) n
(lldb) thread step-over

Step into

The following commands are equivalent to clicking on the step-into button:

(lldb) step
(lldb) s
(lldb) thread step-in

This will advance execution of the code by one line, and when used at a function call, it will step into the function.

The step-over and step-incommands are only different at a function call, otherwise they behave the same.

Step out

The following commands are equivalent to clicking on the step-out button:

(lldb) finish
(lldb) f
(lldb) thread step-out

Trying it out

Spend some time getting used to stepping through the code using these commands. You'll find that very quickly, your muscle memory starts to do most of the work for you, freeing up some mental capacity for actually dealing with whatever bugs you may be dealing with.

If you use one of the step-over or step-into commands (n and s are the most convenient), you can hit return again to repeat that command, which is a more comfortable way of repeatedly stepping through a debug session.

Enable

The following commands are available for enabling and disabling breakpoints:

br disable
br enable

Enabling breakpoints en massedoes not re-enable those that were disabled individually.

Printing in LLDB

LLDB provides three convenient commands to print details of an object to the console:

  • The p or printcommand gives us a standard description of the object.
  • The pocommand (meaning print object) gives us either a standard description, or a custom description if we have implemented one.
  • The frame variable command prints the same asprint, but does so without executing any code, thus avoiding the danger of side effects.

Preparing classes for LLDB description

We can provide custom classeswith any description we wish, and have it print with the po command, by declaring the class to conform to the CustomDebugStringConvertibleprotocol, and then implementing its required variable debugDescription.

  1. Let's create a Report class, that has a few properties of mixed types, and a debugDescription variable:
    class Report: CustomDebugStringConvertible
    {
      var title:    String?
      var date:     Date?
      var approved: Bool?
    
      var debugDescription: String {
        return "("Report")n(title)n(date)n(approved)"
      }
    }
    

  2. Now create an instance of the class, and one by one, populate the optional properties:

    let report = Report()
    
    report.title = "Weekly Summary"
    report.date = Date()
    report.approved = true

  3. Set a breakpoint at the first of those lines, where the report instance is declared.

  4. Now type po into the LLDB console. You'll get <uninitialized> in the console.

  5. Type s into the console, to step through the code. Keep your eye on the green tinted position pointer in the breakpoint gutter, as it will show you which lines of code are being executed during the initialization of a Report instance.

  6. Type s (or return) another five times until the initialization is complete, and then type po again. At this point we have the report variable initialized, with its properties still set to nil.

    (lldb) po report
    <uninitialized>
    (lldb) s
    (lldb) s
    (lldb) s
    (lldb) s
    (lldb) s
    (lldb) po report
    <uninitialized>
    (lldb) s
    (lldb) po report
    Report
    
    nil
    nil
    nil

  7. Continue stepping through the code, following this session:

    (lldb) s
    (lldb) s
    (lldb) s
    (lldb) po report
    Report
    Optional("Weekly Summary")
    nil
    nil
    etc...

As you step through the code, you can see the report variable being populated with data.

Stop hooks

But what about having an update of an object's state automatically logged to the console everytime the code is stopped? Then we wouldn't need to go through all this typing, and as we continue debugging, we only need to worry about where to set breakpoints.

To do that, we can add a so-called one-liner to a breakpoint that is then attached to a stop hook.

Run the code again, and this time when the code halts, enter the following into the LLDB console:

(lldb) b logReport
(lldb) target stop-hook add --one-liner "po report"

The first line creates a breakpoint and names it logReport.

The second line attaches a command to that breakpoint, equivalent to selecting the debugger commandfrom the Edit Breakpoint... menu as, which does the same as if we had typed po report ourselves.

Now add breakpoints to the next three lines of code, so that the code stops at each:

lldb-and-command-line-img-0

Use the continue command (or just c) to move from one breakpoint to the next, and you'll see that, each time, we get a description of the report current state.

Later on, we'll see that there is a way to log an object's state every time it changes, without it being dependent on halting the code, and without our having to specify where the changes take place.

Printing formattednumbers

LLDB contains its own formatting functionality, so when necessary we can format integers before they are printed to the console.

We can print the integer asis, using the p command:

(lldb) p 111
(Int) $R10 = 111

We can print its binary representation using the p/t command (the t stands for two):

(lldb) p/t 111
(Int) $R11 = 0b0000...lots of zeroes...001101111

A hexadecimal representation is also available, using the p/x command:

(lldb) p/x 111
(Int) $R12 = 0x000000000000006f

An octal representation is available with the p/o command:

(lldb) p/o 111
(Int) $R13 = 0157

Executingcode from LLDB

One of LLDB's great strengths is its ability to reach into the current state of the program and alter it.

So, for example, we could change the title property of our report object while the code is halted on a later breakpoint, with this code:

expr report.title = "Monthly Summary"

After this, the code runs as if "Monthly Summary" had been entered all along.

This is an essential tool to master when debugging; the ability to change values on the fly can save you hundreds of program relaunches in a single day's debugging or prototyping.

Type lookups

This one is short, but sweet. Using the typelookup command, we get a summary of whatever type we may be interested in.

Taking a very small class, our own Report class, as an example, type the following into LLDB:

(lldb) type lookup Report

This will produce output to the console like the following:

class Report : CustomDebugStringConvertible {
  var title: Swift.String?
  var date: Foundation.Date?
  var approved: Swift.Bool?
  var debugDescription: Swift.String {
    get {}
  }
  @objc deinit
  init()
}

Now try it again with a few other types. Try this:

(lldb) type lookup Array

We won't reproduce the output here (it's quite extensive), but it will give you an idea of just how much information is only an LLDB command away.

Breakpoints in LLDB

We can add breakpoints of all types, and configure them to our requirements, from within LLDB, and with a little practice you'll find this is a whole lot quicker (as well as being more flexible) than clicking in the breakpoint gutter, right-clicking the breakpoint, and wading through the breakpoint edit window (which rather bizarrely is extremely difficult not to dismiss accidentally).

It might take a while before you're convinced of this, but try to remember the time that you quit Macintosh or OS X apps using the menus.

Adding a breakpoint

To set a breakpoint that is in the current scope, use this command:

(lldb) breakpoint set --line 56

This can be abbreviated to:

(lldb) b 56

To set a breakpoint somewhere else, add the name of the file after the --file option:

(lldb) breakpoint set --file AppDelegate.swift --line 17

You can also set a breakpoint by specifying a method name:

(lldb) breakpoint set --method testBreakpoints

Imagine setting two dozen breakpoints, across a number of files, some by name, some by line number, and doing it all with the mouse. This console business seriously is faster.

Breakpoint list

We can inspect a detailed list of all the breakpoints, of whatever type, and whether they are user or project bound breakpoints, with the following command:

(lldb) breakpoint list

Or its shorter version:

(lldb) br li

Attaching commands to breakpoints

Using the breakpoint list, take note of one of the breakpoints (set an extra one if you have to), which you will alter once you have hit the first breakpoint set in your code.

With the program execution halted on that first breakpoint, type the following into the LLDB console (using a breakpoint number from your own breakpoint list):

(lldb) breakpoint command add 4.1

You will be prompted to add the command:

Enter your debugger command(s).  Type 'DONE' to end.
>

So, do as it says:

Enter your debugger command(s).  Type 'DONE' to end.
>bt
> DONE

We have chosen bt here, as it's a nice big dump of data onto the console; we won't miss it.

Now type:

Unlock access to the largest independent learning library in Tech for FREE!
Get unlimited access to 7500+ expert-authored eBooks and video courses covering every tech area you can think of.
Renews at £16.99/month. Cancel anytime
(lldb) c

to continue execution, and the program will continue, until it halts at breakpoint 4.1 (or whichever breakpoint you added the command to), and prints the backtrace to the console.

Creating a Swift Error breakpoint

We saw that we can select Swift Error from the breakpoint types list at the bottom of the breakpoint navigator pane (using the + button), and this can also be done (substantially more quickly) directly in LLDB.

(lldb) breakpoint set -E swift

Or we can abbreviate this to one of the following:

(lldb) br s -E swift
(lldb) b -E swift

We can also restrict the breakpoint to a specific error type:

(lldb) b -E swift -O myError

Naming breakpoints

You can create de facto groups of breakpoints by naming them. Multiple breakpoints can share a name, and by referring to that name you can enable, disable and delete all those breakpoints with a single command:

(lldb) br set -n testStepCommands -N group1
Breakpoint 3: 2 locations.
(lldb) br set -n testBreakpoints -N group1
Breakpoint 4: 2 locations.
(lldb) breakpoint disable group1
2 breakpoints disabled.

Having created two breakpoints that share the name group1, we then disable them by passing the breakpointdisable command the name of the group.

Watchpoints

We mentioned earlier that we were going to see a way to log any changes tovariables without having to stop the code. This is where we do that.

We could, of course, use the variable's setter and getter methods to do this, but the point here is that we don't need to change the program's code and then recompile etc.; all of this can be done without restarting the app.

The syntax and console output of watchpoints is generally similar to that of breakpoints, as is made clear from the following input (and console output):

(lldb) watchpoint set variable x

LLDB confirms watchpoint creation with the details of the watchpoint:

Watchpoint created: Watchpoint 1: addr = 0x7fff5fbfec48 size = 8 state = enabled type = w
    declare @ '/Users/stu/Documents/Books/myBooks/Packt/macOS Programming/Content/ch17 LLDB CLI/5623_17_code/5623_17_code/ViewController.swift:93'
    watchpoint spec = 'x'

When you run code containing a watchpoint, you are basically creating a breakpoint that is attached to a variable rather than a line of code.

Adding conditions and commands

You can also attach conditions to watchpoint, just as you can to a breakpoint:

(lldb) watchpoint modify -c (x==2)

And similarly, command add works just as you would expect it to:

(lldb) watchpoint command add 1
Enter your debugger command(s).  Type 'DONE' to end.
>print x
> DONE

So now you can monitor the progress and state of a variable without disturbing the code at all.

In the preceding code, we simply assign a debugger command to watchpoint 1;but how did we get the number of the watchpoint? By checking the watchpoint list.

Watchpoint lists

Just as for breakpoints, we can get a list of watchpoints, using the following command:

(lldb) watchpoint list

This gives us a detailed list of all watchpoints, similar to the list below:

Number of supported hardware watchpoints: 4
Current watchpoint:
Watchpoint 1: addr = 0x7fff5fbfec48 size = 8 state = enabled type = w
    declare @ '/Users/stu/Documents/Books/myBooks/Packt/macOS Programming/Content/ch17 LLDB CLI/5623_17_code/5623_17_code/ViewController.swift:93'
    watchpoint spec = 'x'

Watchpoints must be small. An Int of Bool is okay, a String object will be too large.

Enabling, disabling, and deleting watchpoints

The syntax for disabling, enabling and deleting watchpoints is the same as for breakpoints:

(lldb) watchpoint disable
All watchpoints disabled. (1 watchpoints)

This disables watchpoints only, not breakpoints

(lldb) watchpoint delete
About to delete all watchpoints, do you want to do that?: [Y/n] 
y
All watchpoints removed. (1 watchpoints)

Persistent customization

If you want LLDB to run commands as it starts up, like defining a list of watchpoints, breakpoints (and a whole ton of other stuff), you can place them in the .lldbinitfile, at these paths:

  • ~.lldbinit-Xcode for LLDB within the Xcode debug console
  • ~/.lldbinit for LLDB in the command line

My .initlldb file contains a group of named symbolic breakpoints for things such as viewDidLoad, which are of use in most projects, but which are also disabled by the same file. When I need them for debugging I just need to enable them by name, as opposed to define them from scratch. This is particularly appropriate when breakpoints and watchpoints are a complicated combination of conditions, commands and so on.

Getting help in LLBD

The following command will provide you with the online docs regarding breakpoints and watchpoints:

(lldb) help breakpoint
(lldb) help watchpoint

Of course, you can access the complete help pages with the following:

(lldb) help

Try to get accustomed to using the help pages; they contain a lot of information that is otherwise difficult (or tedious) to find, and overcoming any (understandable) reticence to dive into them is better done sooner rather than later.

Using shell commands in LLDB

You can send shell commands to the system without leaving LLDB by using the platform shell command:

(lldb) platform shell pwd
/path/to/current/directory...

Often, being able to type in these one liners will save you breaking the flow while opening up a Terminal window, navigating to the current directory and so on. Given that shell commands can achieve almost anything on Unix-based machines, you might be tempted to spend a fair amount of time getting used to doing this. Forgot to launch your local server? No problem, just drop into the shell and fire up the server, and then return to the debug session.

REPL in LLDB

The Swift REPL also runs in LLDB.

Actually, whenever you use the Swift REPL, it is running on LLDB: The next time you're running the REPL in a terminal window, try typing a colon, and you'll suddenly discover you were running LLDB all along.

To the Swift REPL it from the LLDB console type repl:

(lldb) repl
1>

Now the REPL prompt is awaiting input. A session might look something like this:

(lldb) repl
1>var a = [1,2]
a: [Int] = 2 values {
  [0] = 1
  [1] = 2
}
2>a.append(3)
3>a
$R0: [Int] = 3 values {
  [0] = 1
  [1] = 2
  [2] = 3
}
4>

This come is really handy when you need to check an idea in Swift while you are, for example, in the middle of an LLDB session.

Switching between LLDB and Swift REPL

While in the REPL, you can still punch through to the underlying LLDB session by prepending a command with a colon:

2>:type lookup Report
class Report : CustomDebugStringConvertible {
  var title: Swift.String?
  var date: Foundation.Date?
  var approved: Swift.Bool?
  var debugDescription: Swift.String {
    get {}
  }
  @objc deinit
  init()
}

In this example, we’ve printed a summary of our Report class (from a REPL session that knows nothing of that class) by using LLDB's type lookup command preceded by the colon.

Leaving a REPL session

To leave the REPL and return to LLDB, type just a colon on its own:

(lldb) repl
4>print("repl says hi")
repl says hi
5>:
(lldb)

The REPL session has not ended, however. If you re-enter it with the repl command, the variables you defined previously are still in scope. As long as you don't terminate the LLDB session, you can switch between the two as necessary.

Using Python in LLDB

LLDB has full, built-in Python support. If you type script into the LLDB console, it will open a Python REPL:

(lldb) script
Python Interactive Interpreter. To exit, type 'quit()', 'exit()'.
>>>

This is about as powerful a scripting language as you are likely to come across, and now it's just waiting there for you to use it. We'll see an example of what it can do shortly, once we have seen how to reach into the debug session from within the Python REPL.

Accessing program variables from Python

The Python REPL has a number of tools with which you can reach into your program's session:

>>> mynumber = lldb.frame.FindVariable("i")
>>> mynumber.GetValue()
'42'

So, we have the entire Python scripting language at our disposal, with access to the running program's variables. How we could attach Python scripts to a breakpoint; now we can run spontaneous ad hoc Python sessions as well. Pretty amazing stuff!

Switching between LLDB and Python REPL

Enter quit to leave the Python REPL.

However, just as we saw with the Swift REPL, the Python REPL session is not killed as long as the LLDB session is not terminated:

(lldb) script
Python Interactive Interpreter. To exit, type 'quit()', 'exit()'.
>>>a = 1
>>>quit
(lldb) script
Python Interactive Interpreter. To exit, type 'quit()', 'exit()'.
>>>a
1
>>>

This ability to switch seamlessly between the two without interrupting either LLDB or the REPL makes this a killer feature of LLDB.

One liners from LLDB to Python

You can also pass a line of Python to the script command and have it executed without entering the REPL:

(lldb) script import os
(lldb) script os.system("open http://www.apple.com")

Once again, we see an enormous amount of scripting power at our disposal.

Getting help in Python

There is a pretty huge help document available with the following command from within LLDB:

(lldb) script help(lldb)

Or use the following command for a smaller, more manageable doc set:

(lldb) script help(lldb.process)

From these documents you have access to more specific topics about Python in LLDB.

It has to be said that these docs are going to be of more use to an experienced Python programmer who is integrating Python into his or her workflow. To learn Python from scratch (and you should, it's easy), you're better off checking out more tutorial-orientated resources.

Altogether now

You can see that from the debug console, you can run LLDB, shell commands, the Swift REPL, and a Python REPL, all without having to kill the running debug session or fire up the Terminal. Once you get comfortable with making use of this, it becomes an indispensable part of the development process.

Standalone LLDB

Starting with Xcode 8, LLDB can run in a standalone Terminal session. To launch it, type the lldb command:

~ lldb
(lldb)

This is great for exploring LLDB, but prototyping and debugging an Xcode project is what we are here for, so let's do that next.

Running Xcode projects in a standalone LLDB session

To debug an Xcode project, we first need the path of the debug build, which we can get by right-clicking on theProductsfolder in the projectnavigator, and selecting Show in Finder:

lldb-and-command-line-img-1

Now add that path as the argument to the lldb command (dragging the build from the Finder into the Terminal window is a pretty quick way to do that):

~ lldb /Users/.../Build/Products/Debug/5623_17_code.app

Hit return. This will produce something similar to the following output:

(lldb) target create "/Users/.../Build/Products/Debug/5623_17_code.app"
Current executable set to '/Users/.../Build/Products/Debug/5623_17_code.app' (x86_64).

We haven't got the code running yet, and we'll set a breakpoint before we do:

(lldb) breakpoint set --file ViewController.swift --line 31
Breakpoint 2: where = 5623_17_code`_623_17_code.ViewController.viewDidLoad () -> () + 12 at ViewController.swift:31, address = 0x0000000100001bdc

Now run the code:

(lldb) run

Once we hit that breakpoint, something like the following output will be generated:

Process 32522 launched: '/Users/.../Build/Products/Debug/5623_17_code.app/Contents/MacOS/5623_17_code' (x86_64)
Process 32522 stopped
* thread #1: tid = 0x2039e3, 0x0000000100001bdc 5623_17_code`ViewController.viewDidLoad(self=0x0000000100a69620) -> () + 12 at ViewController.swift:31, 
queue = 'com.apple.main-thread', 
stop reason = breakpoint 2.1
    frame #0: 0x0000000100001bdc 5623_17_code`ViewController.viewDidLoad(self=0x0000000100a69620) -> () + 12 at ViewController.swift:31
   28  
   29    override func viewDidLoad()
   30    {
-> 31      super.viewDidLoad()
   32  
   33      let report = Report()
   34      report.title = "Weekly Summary"
(lldb)

As we can see (there's even a little arrow and a code excerpt), the code has been stopped at line 31, as required.

Let's print details of the view controller's viewproperty while we're here:

(lldb) p self.view
(NSView) $R0 = 0x0000000100d18070 {
  AppKit.NSResponder = {
    baseNSObject@0 = {
      isa = NSView
    }
    _nextResponder = 0x0000000100d324f0
  }
}
(lldb)

Now we can type c to continue the program's execution;but what happens then is that the terminal's input is no longer going to LLDB, which you will notice by the absence of the (lldb) prompt. It is going to the running program.

You need to typecontrol + C to get back to LLDB.

To return to using the terminal for input to the program, type c to continue. Using these two commands, you can toggle between LLDB and the running process, should you need to.

Differences between standalone and Xcode LLDB

There are a few important things to note about running standalone LLDB sessions:

  • An LLDB session that is running in a Terminal window ignores all Xcode breakpoints (and other sessions).
  • A command line session is available all of the time, without necessarily having to halt the program's execution.
  • We can have two (or more) open processes at the same time by launching from different terminal sessions (that isTerminal app windows). This means we can try variations in our code at the same time:
    • We can use different variable values
    • We can set different breakpoints and watchpoints

Some of these differences make clear the advantages that a standalone session offers over LLDB running in Xcode. We won't necessarily need these features all the time, but it's a good idea to get comfortable with debugging in a separate app. When you do need those features, you'll be glad you did.

Summary

In this article, we have had more than a superficial glimpse of Xcode and LLDB's advanced features, though there is more to discover.

You have learned the following in this article:

  • Using LLDB's advanced debugging features
  • Making your custom classes debug-printable
  • Running your projects in standalone LLDB sessions
  • Using stop hooks and watchpoints in addition to breakpoints
  • Using shell, Swift, and Python commands in an LLDB session in addition to LLDB's own command set

Resources for Article: 


Further resources on this subject: