5.
Expression
Written by Derek Selander
Now that you’ve learned how to set breakpoints so the debugger will stop in your code, it’s time to get useful information out of whatever software you’re debugging.
You’ll often want to inspect instance variables of objects. But, did you know you can even execute arbitrary code through LLDB? What’s more, by using the Swift/Objective-C APIs you can declare, initialize, and inject code all on the fly to help aid in your understanding of the program.
In this chapter you’ll learn about the expression command. This allows you to execute arbitrary code in the debugger.
Formatting p and po
You might be familiar with the go-to debugging command, po. po is often used in Swift & Objective-C code to print out an item of interest. This could be an instance variable in an object, a local reference to an object, or a register, as you’ve seen earlier in this book. It could even be an arbitrary memory reference — so long as there’s an object at that address!
If you do a quick help po in the LLDB console, you’ll find po is actually a shorthand expression for expression -O --. The -O arugment is used to print the object’s description.
po’s often overlooked sibling, p, is another abbreviation with the -O option omitted, resulting in expression --. The format of what p will print out is more dependent on the LLDB type system. LLDB’s type formatting of values helps determine its output and is fully customizable (as you’ll see in a second).
It’s time to learn how the p and po commands get their content. You’ll continue using the Signals project for this chapter.
Start by opening the Signals project in Xcode. Next, open MasterViewController.swift and add the following code above viewDidLoad():
override var description: String {
return "Yay! debugging " + super.description
}
In viewDidLoad, add the following line of code below super.viewDidLoad():
print("\(self)")
Now, put a breakpoint just after the print method you created in the viewDidLoad() of MasterViewController.swift. Do this using the Xcode GUI breakpoint side panel.
Build and run the application.
Once the Signals project stops at viewDidLoad(), type the following into the LLDB console:
(lldb) po self
You’ll get output similar to the following:
Yay! debugging <Signals.MasterViewController: 0x7f8a0ac06b70>
Take note of the output of the print statement and how it matches the po self you just executed in the debugger.
You can also take it a step further. NSObject has an additional method description used for debugging called debugDescription. Add the following below your description variable definition:
override var debugDescription: String {
return "debugDescription: " + super.debugDescription
}
Build and run the application. When the debugger stops at the breakpoint, print self again:
(lldb) po self
The output from the LLDB console will look similar to the following:
debugDescription: Yay! debugging <Signals.MasterViewController: 0x7fb71fd04080>
Notice how the po self and the output of self from the print command now differ, since you implemented debugDescription. When you print an object from LLDB, it’s debugDescription that gets called, rather than description. Neat!
As you can see, having a description or debugDescription when working with an NSObject class or subclass will influence the output of po.
So which objects override these description methods? You can easily hunt down which objects override these methods using the image lookup command with a smart regex query. Your learnings from previous chapters are already coming in handy!
For example, if you wanted to know all the Objective-C classes that override debugDescription, you can simply query all the methods by typing:
(lldb) image lookup -rn '\ debugDescription\]'
Based upon the output, it seems the authors of the Foundation framework have added the debugDescription to a lot of foundation types (i.e. NSArray), to make our debugging lives easier. In addition, they’re also private classes that have overridden debugDescription methods as well.
You may notice one of them in the listing is CALayer. Let’s take a look at the difference between description and debugDescription in CALayer.
In your LLDB console, type the following:
(lldb) po self.view!.layer.description
You’ll see something similar to the following:
"<CALayer: 0x600002e9eb00>"
That’s a little boring. Now type the following:
(lldb) po self.view!.layer
You’ll see something similar to the following:
<CALayer:0x600002e9eb00; position = CGPoint (187.5 406); bounds = CGRect (0 0; 375 812); delegate = <UITableView: 0x7fc25c01c600; frame = (0 0; 375 812); clipsToBounds = YES; autoresize = W+H; gestureRecognizers = <NSArray: 0x6000020cf240>; layer = <CALayer: 0x600002e9eb00>; contentOffset: {0, 0}; contentSize: {0, 0}; adjustedContentInset: {0, 0, 0, 0}>; sublayers = (<CALayer: 0x600002e9f2a0>, <CALayer: 0x600002e9f340>); masksToBounds = YES; allowsGroupOpacity = YES; backgroundColor = <CGColor 0x600000a88b40> [<CGColorSpace 0x600000a83b40> (kCGColorSpaceICCBased; kCGColorSpaceModelRGB; sRGB IEC61966-2.1; extended range)] ( 0.980392 0.980392 0.980392 1 ); _uikit_viewPointer = <UITableView: 0x7fc25c01c600; frame = (0 0; 375 812); clipsToBounds = YES; autoresize = W+H; gestureRecognizers = <NSArray: 0x6000020cf240>; layer = <CALayer: 0x600002e9eb00>; contentOffset: {0, 0}; contentSize: {0, 0}; adjustedContentInset: {0, 0, 0, 0}>>
That’s much more interesting — and much more useful! Obviously the developers of Core Animation decided the plain description should be just the object reference, but if you’re in the debugger, you’ll want to see more information. It’s unclear exactly why they did this. It might be some of the information in the debug description is expensive to calculate, so they only want to do it when absolutely necessary.
Next, while you’re still stopped in the debugger (and if not, get back to the viewDidLoad() breakpoint), execute the p command on self, like so:
(lldb) p self
You’ll get something similar to the following:
(Signals.MasterViewController) $R2 = 0x00007fc25b80e6e0 {
UIKit.UITableViewController = {
baseUIViewController@0 = <extracting data from value failed>
_tableViewStyle = 0
_keyboardSupport = nil
_staticDataSource = nil
_filteredDataSource = 0x00006000020ceee0
_filteredDataType = 0
}
detailViewController = nil
}
This might look scary, but let’s break it down.
First, LLDB spits out the class name of self. In this case, Signals.MasterViewController.
Next follows a reference you can use to refer to this object from now on within your LLDB session. In the example above, it’s $R2. Yours will vary as this is a number LLDB increments as you use LLDB.
This reference is useful if you ever want to get back to this object later in the session, perhaps when you’re in a different scope and self is no longer the same object. In that case, you can refer back to this object as $R2. To see how, type the following:
(lldb) p $R2
You’ll see the same information printed out again. You’ll learn more about these LLDB variables later in this chapter.
After the LLDB variable name is the address to this object, followed by some output specific to this type of class. In this case, it shows the details relevant to UITableViewController, which is the superclass of MasterViewController, followed by the detailViewController instance variable.
As you can see, the meat of the output of the p command is different to the po command. The output of p is dependent upon type formatting: internal data structures the LLDB authors have added to every (noteworthy) data structure in Objective-C, Swift, and other languages. It’s important to note the formatting for Swift is under active development with every Xcode release, so the output of p for MasterViewController might be different for you.
Since these type formatters are held by LLDB, you have the power to change them if you so desire. In your LLDB session, type the following:
(lldb) type summary add Signals.MasterViewController --summary-string "Wahoo!"
You’ve now told LLDB you just want to return the static string, "Wahoo!", whenever you print out an instance of the MasterViewController class. The Signals prefix is essential for Swift classes since Swift includes the module in the classname to prevent namespace collisions. Try printing out self now, like so:
(lldb) p self
The output should look similar to the following:
(lldb) (Signals.MasterViewController) $R3 = 0x00007fb71fd04080 Wahoo!
This formatting will be remembered by LLDB across app launches, so be sure to remove it when you’re done playing with the p command.
Remove yours from your LLDB session like so:
(lldb) type summary clear
Typing p self will now go back to the default implementation created by the LLDB formatting authors.
Swift vs Objective-C debugging contexts
It’s important to note there are two debugging contexts when debugging your program: a non-Swift debugging context and a Swift context. By default, when you stop in Objective-C code, LLDB will use the non-Swift (Objective-C) debugging context, while if you’re stopped in Swift code, LLDB will use the Swift context. Sounds logical, right?
If you stop the debugger out of the blue (for example, if you press the process pause button in Xcode), LLDB will choose the Objective-C context by default.
Make sure the GUI Swift breakpoint you’ve created in the previous section is still enabled and build and run the app. When the breakpoint hits, type the following into your LLDB session:
(lldb) po [UIApplication sharedApplication]
LLDB will throw a cranky error at you:
error: <EXPR>:3:16: error: expected ',' separator
[UIApplication sharedApplication]
^
,
You’ve stopped in Swift code, so you’re in the Swift context. But you’re trying to execute Objective-C code. That won’t work. Similarly, in the Objective-C context, doing a po on a Swift object will not work.
You can force the expression to be used in the Objective-C context with the -l option to select the language. However, since the po expression is mapped to expression -O --, you’ll be unable to use the po command since the arguments you provide come after the --, which means you’ll have to type out the expression. In LLDB, type the following:
(lldb) expression -l objc -O -- [UIApplication sharedApplication]
Here you’ve told LLDB to use the objc language for Objective-C. You can also use objc++ for Objective-C++ if necessary.
LLDB will spit out the reference to the shared application. Try the same thing in Swift. Since you’re already stopped in the Swift context, try to print the UIApplication reference using Swift syntax, like so:
(lldb) po UIApplication.shared
You’ll get the same output as you did printing with the Objective-C context. Resume the program, by typing continue, then pause the Signals application out of the blue.
From there, press the up arrow to bring up the same Swift command you just executed and see what happens:
(lldb) po UIApplication.shared
Again, LLDB will be cranky:
error: property 'shared' not found on object of type 'UIApplication'
Remember, stopping out of the blue will put LLDB in the Objective-C context. That’s why you’re getting this error when trying to execute Swift code.
You should always be aware of the language in which you are currently paused in the debugger.
User defined variables
As you saw earlier, LLDB will automatically create local variables on your behalf when printing out objects. You can create your own variables as well.
Remove all the breakpoints from the program and build and run the app. Stop the debugger out of the blue so it defaults to the Objective-C context. From there type:
(lldb) po id test = [NSObject new]
LLDB will execute this code, which creates a new NSObject and stores it to the test variable. Now, print the test variable in the console:
(lldb) po test
You’ll get an error like the following:
error: use of undeclared identifier 'test'
This is because you need to prepend variables you want LLDB to remember with the $ character.
Declare test again with the $ in front:
(lldb) po id $test = [NSObject new]
(lldb) po $test
<NSObject: 0x60000001d190>
This variable was created in the Objective-C object. But what happens if you try to access this from the Swift context? Try it, by typing the following:
(lldb) expression -l swift -O -- $test
So far so good. Now try executing a Swift-styled method on this Objective-C class.
(lldb) expression -l swift -O -- $test.description
You’ll get an error like this:
error: <EXPR>:3:1: error: use of unresolved identifier '$test'
$test.description
^~~~~
If you create an LLDB variable in the Objective-C context, then move to the Swift context, don’t expect everything to “just work.” This is an area under active development and the bridging between Objective-C and Swift through LLDB will likely see improvements over time.
So how could creating references in LLDB actually be used in a real life situation? You can grab the reference to an object and execute (as well as debug!) arbitrary methods of your choosing. To see this in action, create a symbolic breakpoint on MasterViewController’s parent view controller, MasterContainerViewController using an Xcode symbolic breakpoint for MasterContainerViewController’s viewDidLoad.
In the Symbol section, type the following:
Signals.MasterContainerViewController.viewDidLoad() -> ()
Be aware of the spaces for the parameters and parameter return type, otherwise the breakpoint will not work.
Your breakpoint should look like the following:
Build and run the app. Xcode will now break on MasterContainerViewController.viewDidLoad(). From there, type the following:
(lldb) p self
Since this is the first argument you executed in the Swift debugging context, LLDB will create the variable, $R0. Resume execution of the program by typing continue in LLDB.
Now you don’t have a reference to the instance of MasterContainerViewController through the use of self since the execution has left viewDidLoad() and moved on to bigger and better run loop events.
Oh, wait, you still have that $R0 variable! You can now reference MasterContainerViewController and even execute arbitrary methods to help debug your code.
Pause the app in the debugger manually, then type the following:
(lldb) po $R0.title
Unfortunately, you get:
error: use of undeclared identifier '$R0'
You stopped the debugger out of the blue! Remember, LLDB will default to Objective-C; you’ll need to use the -l option to stay in the Swift context:
(lldb) expression -l swift -- $R0.title
The output will be similar to the following, you might have a different R number:
(String?) $R1 = "Quarterback"
Of course, this is the title of the view controller, shown in the navigation bar.
Now, type the following:
(lldb) expression -l swift -- $R0.title = "💩💩💩💩💩"
Resume the app by typing continue or pressing the play button in Xcode.
Note: To quickly access a poop emoji on your macOS machine, hold down
⌘ + ⌃ + space. From there, you can easily hunt down the correct emoji by searching for the phrase “poop.”
It’s the small things in life you cherish!
As you can see, you can easily manipulate variables to your will.
In addition, you can also create a breakpoint on code, execute the code, and cause the breakpoint to be hit. This can be useful if you’re in the middle of debugging something and want to step through a function with certain inputs to see how it operates.
For example, you still have the symbolic breakpoint in viewDidLoad(), so try executing that method to inspect the code. Pause execution of the program, then type:
(lldb) expression -l swift -O -- $R0.viewDidLoad()
Nothing happened. The breakpoint didn’t hit. What gives? In fact, MasterContainerViewController did execute the method, but by default, LLDB will ignore any breakpoints when executing commands. You can disable this option with the -i option.
Type the following into your LLDB session:
(lldb) expression -l swift -O -i 0 -- $R0.viewDidLoad()
LLDB will now break on the viewDidLoad() symbolic breakpoint you created earlier. This tactic is a great way to test the logic of methods. For example, you can implement test-driven debugging, by giving a function different parameters to see how it handles different input.
Type formatting
One of the nice options LLDB has is the ability to format the output of basic data types. This makes LLDB a great tool to learn how the compiler formats basic C types. This is a must to know when you’re exploring the assembly section, which you’ll do later in this book.
First, remove the previous symbolic breakpoint. Next, build and run the app and finally pause the debugger out of the blue to make sure you’re in the Objective-C context.
Type the following into your LLDB session:
(lldb) expression -G x -- 10
This -G option tells LLDB what format you want the output in. The G stands for GDB format. If you’re not aware, GDB is the debugger that preceded LLDB. This therefore is saying whatever you specify is a GDB format specifier. In this case, x is used which indicates hexadecimal.
You’ll see the following output:
(int) $0 = 0x0000000a
This is decimal 10 printed as hexadecimal. Wow!
But wait! There’s more! LLDB lets you format types using a neat shorthand syntax. Type the following:
(lldb) p/x 10
You’ll see the same output as before. But that’s a lot less typing!
This is great for learning the representations behind C datatypes. For example, what’s the binary representation of the integer 10?
(lldb) p/t 10
The /t specifies binary format. You’ll see what decimal 10 looks like in binary. This can be particularly useful when you’re dealing with a bit field for example, to double check what fields will be set for a given number.
What about negative 10?
(lldb) p/t -10
Decimal 10 in two’s complement. Neat!
What about the floating point binary representation of 10.0?
(lldb) p/t 10.0
That could come in handy!
How about the ASCII value of the character ’D’?
(lldb) p/d 'D'
Ah so ’D’ is 68! The /d specifies decimal format.
Finally, what is the acronym hidden behind this integer?
(lldb) p/c 1430672467
The /c specifies char format. It takes the number in binary, splits into 8 bit (1 byte) chunks, and converts each chunk into an ASCII character. In this case, it’s a 4 character code (FourCC), saying STFU. Hey! Be nice now!
The full list of output formats is as follows (taken from https://sourceware.org/gdb/onlinedocs/gdb/Output-Formats.html):
-
x: hexadecimal -
d: decimal -
u: unsigned decimal -
o: octal -
t: binary -
a: address -
c: character constant -
f: float -
s: string
If these formats aren’t enough for you, you can use LLDB’s extra formatters, although you’ll be unable to use the GDB formatting syntax.
LLDB’s formatters can be used like this:
(lldb) expression -f Y -- 1430672467
This gives you the following output:
(int) $0 = 53 54 46 55 STFU
This explains the FourCC code from earlier!
LLDB has the following formatters (taken from http://lldb.llvm.org/varformats.html):
-
B: boolean -
b: binary -
y: bytes -
Y: bytes with ASCII -
c: character -
C: printable character -
F: complex float -
s: c-string -
i: decimal -
E: enumeration -
x: hex -
f: float -
o: octal -
O: OSType -
U: unicode16 -
u: unsigned decimal -
p: pointer
Where to go from here?
Pat yourself on the back — this was another jam-packed round of what you can do with the expression command. Try exploring some of the other expression options yourself by executing help expression and see if you can figure out what they do.