Hide chapters

Advanced Apple Debugging & Reverse Engineering

Third Edition · iOS 12 · Swift 4.2 · Xcode 10

Before You Begin

Section 0: 3 chapters
Show chapters Hide chapters

Section III: Low Level

Section 3: 7 chapters
Show chapters Hide chapters

Section IV: Custom LLDB Commands

Section 4: 8 chapters
Show chapters Hide chapters

5. Expression
Written by Derek Selander

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

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():


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?

(lldb) po [UIApplication sharedApplication]
error: <EXPR>:3:16: error: expected ',' separator
[UIApplication sharedApplication]
(lldb) expression -l objc -O -- [UIApplication sharedApplication] 
(lldb) po UIApplication.shared
(lldb) po UIApplication.shared
error: property 'shared' not found on object of type 'UIApplication'

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.

(lldb) po id test = [NSObject new]
(lldb) po test 
error: use of undeclared identifier 'test'
(lldb) po id $test = [NSObject new] 
(lldb) po $test 
<NSObject: 0x60000001d190>
(lldb) expression -l swift -O -- $test 
(lldb) expression -l swift -O -- $test.description
error: <EXPR>:3:1: error: use of unresolved identifier '$test'
Signals.MasterContainerViewController.viewDidLoad() -> ()

(lldb) p self 
(lldb) po $R0.title
error: use of undeclared identifier '$R0'
(lldb) expression -l swift -- $R0.title
(String?) $R1 = "Quarterback"
(lldb) expression -l swift -- $R0.title = "💩💩💩💩💩"

(lldb) expression -l swift -O -- $R0.viewDidLoad()
(lldb) expression -l swift -O -i 0 -- $R0.viewDidLoad()

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.

(lldb) expression -G x -- 10
(int) $0 = 0x0000000a
(lldb) p/x 10
(lldb) p/t 10
(lldb) p/t -10
(lldb) p/t 10.0
(lldb) p/d 'D'
(lldb) p/c 1430672467
(lldb) expression -f Y -- 1430672467
(int) $0 = 53 54 46 55             STFU

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.

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2024 Kodeco Inc.

You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now