4.
Stopping in Code
Written by Derek Selander
Whether you’re using Swift, Objective-C, C++, C, or an entirely different language in your technology stack, you’ll need to learn how to create breakpoints. It’s easy to click on the side panel in Xcode to create a breakpoint using the GUI, but the LLDB console can give you much more control over breakpoints.
In this chapter, you’re going to learn all about breakpoints and how to create them using LLDB.
Signals
For this chapter, you’ll be looking at a project I’ve supplied; it’s called Signals and you’ll find it in the resources bundle for this chapter.
Open up the Signals project using Xcode. Signals is a basic master-detail project themed as an American football app that displays some rather nerdily-named offensive play calls.
Internally, this project montors several Unix signals and displays them when the Signals program receives them.
Unix signals are a basic form of interprocess communication. For example, one of the signals, SIGSTOP, can be used to save the state and pause execution of a process, while its counterpart, SIGCONT, is sent to a program to resume execution. Both of these signals can be used by a debugger to pause and continue a program’s execution.
This is an interesting application on several fronts, because it not only explores Unix signal handling, but also highlights what happens when a controlling process (LLDB) handles the passing of Unix signals to the controlled process. By default, LLDB has custom actions for handling different signals. Some signals are not passed onto the controlled process while LLDB is attached.
In order to display a signal, you can either raise a Signal from within the application, or send a signal externally from a different application, like Terminal.
In addition, there’s a UISwitch that toggles the signal handling. When the switch is toggled, it calls a C function sigprocmask to disable or enable the signal handlers.
Finally, the Signal application has a Timeout bar button which raises the SIGSTOP signal from within the application, essentially “freezing” the program. However, if LLDB is attached to the Signals program (and by default it will be, when you build and run through Xcode), calling SIGSTOP will allow you to inspect the execution state with LLDB while in Xcode.
Select your iOS Simulator of choice while making sure the iOS Simulator is at least version iOS 12.0 or greater. Build and run the app. Once the project is running, navigate to the Xcode console and pause the debugger.
Resume Xcode and keep an eye on the Simulator. A new row will be added to the UITableView whenever the debugger stops then resumes execution. This is achieved by Signals monitoring the SIGSTOP Unix signal event and adding a row to the data model whenever it occurs. When a process is stopped, any new signals will not be immediately processed because the program is sort of, well, stopped.
Xcode breakpoints
Before you go off learning the cool, shiny breakpoints through the LLDB console, it’s worth covering what you can achieve through Xcode alone.
Symbolic breakpoints are a great debugging feature of Xcode. They let you set a breakpoint on a certain symbol within your application. An example of a symbol is -[NSObject init], which refers to the init method of NSObject instances.
The neat thing about symbolic breakpoints in Xcode is that once you enter a symbolic breakpoint, you don’t have to type it in again the next time the program launches.
You’re now going to try using a symbolic breakpoint to show all the instances of NSObject being created.
Kill the app if it’s currently running. Next, switch to the Breakpoint Navigator. In the bottom left, click the plus button to select the Symbolic Breakpoint… option.
A pop-up will appear. In the Symbol part of the popup type: -[NSObject init]. Under Action, select Add Action and then select Debugger Command from the dropdown. Next, enter po [$arg1 class] in the box below.
Finally, select Automatically continue after evaluating actions. Your popup should look similar to below:
Build and run the app. Xcode will dump all the names of the classes it initializes while running the Signals program through the console… which, upon viewing, is quite a lot.
What you’ve done here is set a breakpoint that fires each time -[NSObject init] is called. When the breakpoint fires, a command runs in LLDB, and execution of the program continues automatically.
Note: You’ll learn how to properly use and manipulate registers in Chapter 11, “Assembly, Registers and Calling Convention”, but for now, simply know
$arg1is synonymous to the$rdiregister and can be loosely thought of as holding the instance of a class wheninitis called.
Once you’ve finished inspecting all the class names dumped out, delete the symbolic breakpoint by right-clicking the breakpoint in the breakpoint navigator and selecting Delete Breakpoint.
In addition to symbolic breakpoints, Xcode also supports several types of error breakpoints. One of these is the Exception Breakpoint. Sometimes, something goes wrong in your program and it just simply crashes. When this happens, your first reaction to this should be to enable an exception breakpoint, which will fire every time an exception is thrown. Xcode will show you the offending line, which greatly aids in hunting down the culprit responsible for the crash.
Finally, there is the Swift Error Breakpoint, which stops any time Swift throws an error by essentially creating a breakpoint on the swift_willThrow method. This is a great option to use if you’re working with any APIs that can be error-prone, as it lets you diagnose the situation quickly without making false assumptions about the correctness of your code.
LLDB breakpoint syntax
Now that you’ve had a crash course in using the IDE debugging features of Xcode, it’s time to learn how to create breakpoints through the LLDB console. In order to create useful breakpoints, you need to learn how to query what you’re looking for.
The image command is an excellent tool to help introspect details that will be vital for setting breakpoints.
There are two configurations you’ll use in this book for code hunting. The first is the following:
(lldb) image lookup -n "-[UIViewController viewDidLoad]"
This command dumps the implementation address (the offset address of where this method is located within the framework’s binary) of the function for -[UIViewController viewDidLoad]. The -n argument tells LLDB to look up either a symbol or function name.
The output will be similar to below:
1 match found in /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/PrivateFrameworks/UIKitCore.framework/UIKitCore:
Address: UIKitCore[0x0000000000ac813c] (UIKitCore.__TEXT.__text + 11295452)
Summary: UIKitCore`-[UIViewController viewDidLoad]
You can tell that this is iOS 12 due to the location of where the -[UIViewController viewDidLoad] method is located. In previous iOS versions this method was located in UIKit, but has now since moved to UIKitCore likely due to the macOS/iOS unification process, unofficially referred to as Marzipan.
Another useful, similar command is this:
(lldb) image lookup -rn test
This does a case-sensitive regex lookup for the word "test". If the lowercase word "test" is found anywhere, in any function, in any of the modules (i.e. UIKit, Foundation, Core Data, etc) loaded in the current executable (that are not stripped out of a release builds… more on that later), this command will spit out the results.
Note: Use the
-nargument when you want exact matches (with quotes around your query if it contains spaces) and use the-rnarguments to do a regex search. The-nonly command helps figure out the exact parameters to match a breakpoint, especially when dealing with Swift, while the-rnargument option will be heavily favored in this book since a smart regex can eliminate quite a bit of typing — as you’ll soon find out.
Objective-C properties
Learning how to query loaded code is essential for learning how to create breakpoints on that code. Both Objective-C and Swift have specific property signatures when they’re created by the compiler, which results in different querying strategies when looking for code.
For example, the following Objective-C class is declared in the Signals project:
@interface TestClass : NSObject
@property (nonatomic, strong) NSString *name;
@end
The compiler will generate code for both the setter and getter of the property name. The getter will look like the following:
-[TestClass name]
…while the setter would look like this:
-[TestClass setName:]
Build and run the app, then pause the debugger. Next, verify these methods do exist by typing the following into LLDB:
(lldb) image lookup -n "-[TestClass name]"
In the console output, you’ll get something similar to the following:
1 match found in /Users/derekselander/Library/Developer/Xcode/DerivedData/Signals-atqcdyprrotlrvdanihoufkwzyqh/Build/Products/Debug-iphonesimulator/Signals.app/Signals:
Address: Signals[0x0000000100002150] (Signals.__TEXT.__text + 0)
Summary: Signals`-[TestClass name] at TestClass.h:28
LLDB will dump information about the function included in the executable. The output may look scary, but there are some good tidbits here.
Note: The
image lookupcommand can produce a lot of output that can be pretty hard on the eyes when a query matches a lot of code. In Chapter 26, “SB Examples, Improved Lookup”, you’ll build a cleaner alternative to LLDB’simage lookupcommand to save your eyes from looking at too much output.
The console output tells you LLDB was able to find out this function was implemented in the Signals executable, at an offset of 0x0000000100002150 in the __TEXT segment of the __text section to be exact (don’t worry if that didn’t make any sense, you’ll learn all about that later on in the book). LLDB was also able to tell that this method was declared on line 28 in TestClass.h.
You can check for the setter as well, like so:
(lldb) image lookup -n "-[TestClass setName:]"
You’ll get output similar to the previous command, this time showing the implementation address and of the setter’s declaration for name.
Objective-C properties and dot notation
Something that is often misleading to entry level Objective-C (or Swift only) developers is the Objective-C dot notation syntax for properties.
Objective-C dot notation is a somewhat controversial compiler feature that allows properties to use a shorthand getter or setter.
Consider the following:
TestClass *a = [[TestClass alloc] init];
// Both equivalent for setters
[a setName:@"hello, world"];
a.name = @"hello, world";
// Both equivalent for getters
NSString *b;
b = [a name]; // b = @"hello, world"
b = a.name; // b = @"hello, world"
In the above example, the -[TestClass setName:] method is called twice, even with the dot notation. The same can be said for the getter, -[TestClass name]. This is important to know if you’re dealing with Objective-C code and trying to create breakpoints on the setters and getters of properties with dot notation.
Swift properties
The syntax for a property is much different in Swift. Take a look at the code in SwiftTestClass.swift which contains the following:
class SwiftTestClass: NSObject {
var name: String!
}
Make sure the Signals project is running and paused in LLDB. Feel free to clear the LLDB console by typing Command + K in the debug window to start fresh.
In the LLDB console, type the following:
(lldb) image lookup -rn Signals.SwiftTestClass.name.setter
You’ll get output similar to below:
1 match found in /Users/derekselander/Library/Developer/Xcode/DerivedData/Signals-atqcdyprrotlrvdanihoufkwzyqh/Build/Products/Debug-iphonesimulator/Signals.app/Signals:
Address: Signals[0x000000010000bd50] (Signals.__TEXT.__text + 39936)
Summary: Signals`Signals.SwiftTestClass.name.setter : Swift.Optional<Swift.String> at SwiftTestClass.swift:28
Hunt for the information after the word Summary in the output. There are a couple of interesting things to note here.
Do you see how long the function name is!? This whole thing needs to be typed out for one valid Swift breakpoint! If you wanted to set a breakpoint on this setter, you’d have to type the following:
(lldb) b Signals.SwiftTestClass.name.setter : Swift.Optional<Swift.String>
Using regular expressions is an attractive alternative to typing out this monstrosity.
Apart from the length of the Swift function name you produced, note how the Swift property is formed. The function signature containing the property name has the word setter immediately following the property. Perhaps the same convention works for the getter method as well?
Search for the SwiftTestClass setter and getter for the name property, at the same time, using the following regular expression query:
(lldb) image lookup -rn Signals.SwiftTestClass.name
This uses a regex query to dump everything that contains the phrase Signals.SwiftTestClass.name.
Since this is a regular expression, the periods (.) are evaluated as wildcards, which in turn matches periods in the actual function signatures.
You’ll get a fair bit of output, but hone in every time you see the word Summary in the console ouput. You’ll find the output matches the getter, (Signals.SwiftTestClass.name.getter) the setter, (Signals.SwiftTestClass.name.setter), as well as two methods containing materializeForSet, helper methods for Swift constructors.
There’s a pattern for the function names for Swift properties:
ModuleName.Classname.PropertyName.(getter|setter)
The ability to dump methods, find a pattern, and narrow your search scope is a great way to uncover the Swift/Objective-C language internals as you work to create smart breakpoints in your code.
Finally… creating breakpoints
Now you know how to query the existence of functions and methods in your code, it’s time to start creating breakpoints on them.
If you already have the Signals app running, stop and restart the application, then press the pause button to stop the application and bring up the LLDB console.
There are several different ways to create breakpoints. The most basic way is to simply type the letter b followed by the name of your breakpoint. This is fairly easy in Objective-C and C, since the names are short and easy to type (e.g. -[NSObject init] or -[UIView setAlpha:]). They’re quite tricky to type in C++ and Swift, since the compiler turns your methods into symbols with rather long names.
Since UIKit is primarily Objective-C (at the time of this writing at least!), create a breakpoint using the b argument, like so:
(lldb) b -[UIViewController viewDidLoad]
You’ll see the following output:
Breakpoint 1: where = UIKitCore`-[UIViewController viewDidLoad], address = 0x0000000114a4a13c
When you create a valid breakpoint, the console will spit out some information about that breakpoint. In this particular case, the breakpoint was created as Breakpoint 1 since this was the first breakpoint in this particular debugging session. As you create more breakpoints, this breakpoint ID will increment.
Resume the debugger. Once you’ve resumed execution, a new SIGSTOP signal will be displayed. Tap on the cell to bring up the detail UIViewController. The program should pause when viewDidLoad of the detail view controller is called.
Note: Like a lot of shorthand commands,
bis an abbreviation for another, longer LLDB command. Run thehelpwith thebcommand to figure out the actual command yourself and learn all the cool tricksbcan do under the hood.
In addition to the b command, there’s another longer breakpoint set command, which has a slew of options available. You’ll explore these options over the next couple of sections. Many of the commands will stem from various options of the breakpoint set command.
Regex breakpoints and scope
Another extremely powerful command is the regular expression breakpoint, rbreak, which is an abbreviation for breakpoint set -r %1. You can quickly create many breakpoints using smart regular expressions to stop wherever you want.
Going back to the previous example with the egregiously long Swift property function names, instead of typing:
(lldb) b Signals.SwiftTestClass.name.setter : Swift.Optional<Swift.String>
You can simply type:
(lldb) rb SwiftTestClass.name.setter
The rb command will get expanded out to rbreak (provided you don’t have any other LLDB commands that begin with “rb”). This will create a breakpoint on the setter property of name in SwiftTestClass
To be even more brief, you could simply use the following:
(lldb) rb name\.setter
This will produce a breakpoint on anything that contains the phrase name.setter. This will work if you know you don’t have any other Swift properties called name within your project; otherwise you’ll create multiple breakpoints for each class that contains a “name” property that has a setter.
Let’s up the complexity of these regular expressions.
Create a breakpoint on every Objective-C instance method of UIViewController. Type the following into your LLDB session:
(lldb) rb '\-\[UIViewController\ '
The ugly back slashes are escape characters to indicate you want the literal character to be in the regular expression search. As a result, this query breaks on every method containing the string -[UIViewController followed by a space.
But wait… what about Objective-C categories? They take on the form of (-|+)[ClassName(categoryName) method]. You’ll have to rewrite the regular expression to include categories as well.
Type the following into your LLDB session and when prompted type y to confirm:
(lldb) breakpoint delete
This command deletes all the breakpoints you have set.
Next, type the following:
(lldb) rb '\-\[UIViewController(\(\w+\))?\ '
This provides an optional parenthesis with one or more alphanumeric characters followed by a space, after UIViewController in the breakpoint.
Regex breakpoints let you capture a wide variety of breakpoints with a single expression.
You can limit the scope of your breakpoints to a certain file, using the -f option. For example, you could type the following:
(lldb) rb . -f DetailViewController.swift
This would be useful if you were debugging DetailViewController.swift. It would set a breakpoint on all the property getters/setters, blocks/closures, extensions/categories, and functions/methods in this file. -f is known as a scope limitation.
If you were completely crazy and a fan of pain (the doctors call that masochistic?), you could omit the scope limitation and simply do this:
(lldb) rb .
This will create a breakpoint on everything… Yes, everything! This will create breakpoints on all the code in the Signals project, all the code in UIKit as well as Foundation, all the event run loop code that gets fired at (hopefully) 60 hertz — everything. As a result, expect to type continue in the debugger a fair bit if you execute this.
There are other ways to limit the scope of your searches. You can limit to a single library using the -s option:
(lldb) rb . -s Commons
This would set a breakpoint on everything within the Commons library, which is a dynamic library contained within the Signals project.
This is not limited to your code; you can use the same tactic to create a breakpoint on every function in UIKitCore, like so:
(lldb) rb . -s UIKitCore
Even that is still a little crazy. There are a lot of methods — around 86,760 UIKitCore methods in iOS 12.0. How about only stopping on the first method in UIKitCore you hit, and simply continue? The -o option offers a solution for this. It creates what is known as a “one-shot” breakpoint.
When these breakpoints hit, the breakpoint is deleted. So it’ll only ever hit once.
To see this in action, type the following in your LLDB session:
(lldb) breakpoint delete
(lldb) rb . -s UIKitCore -o 1
Note: Be patient while your computer executes this command, as LLDB has to create a lot of breakpoints. Also make sure you are using the Simulator, or else you’ll wait for a very long time!
Next, continue the debugger, and click on a cell in the table view. The debugger stops on the first UIKitCore method this action calls. Finally, continue the debugger, and the breakpoint will no longer fire.
Other cool breakpoint options
The -L option lets you filter by source language. So, if you wanted to only go after Swift code in the Commons module of the Signals application, you could do the following:
(lldb) breakpoint set -L swift -r . -s Commons
This would set a breakpoint on every Swift method within the Commons module.
What if you wanted to go after something interesting around a Swift if let but totally forgot where in your application it is? You can use source regex breakpoints to help figure locations of interest! Like so:
(lldb) breakpoint set -A -p "if let"
This will create a breakpoint on every source code location that contains if let. You can of course get waaaaaay more fancy since the -p takes a regular expression breakpoint to go after complicated expressions. The -A option says to search in all source files known to the project.
If you wanted to filter the above breakpoint query to only MasterViewController.swift and DetailViewController.swift, you could do the following:
(lldb) breakpoint set -p "if let" -f MasterViewController.swift -f DetailViewController.swift
Notice how the -A has gone, and how each -f will let you specify a filename. I am lazy, so I’ll usually default to -A to give me all files and drill in from there.
Finally, you can also filter by a specific module as well. If you wanted to create a breakpoint for “if let” for anything in the Signals executable (while ignoring other frameworks like Commons), you could do this:
(lldb) breakpoint set -p "if let" -s Signals -A
This will grab all source files (-A), but filter those to only the ones that belong to the Signals executable (with the -s Signals option).
One more cool breakpoint option example? OK, you talked me into it. You will make a breakpoint which prints the UIViewController whenever viewDidLoad gets hit, but you’ll do it via LLDB console instead of the Symbolic breakpoint window. Then, you’ll export this breakpoint to a file so you can show how cool you are to your coworkers by using the breakpoint read and breakpoint write commands!
First off, delete all breakpoints:
(lldb) breakpoint delete
Now create the following (complex!) breakpoint:
(lldb) breakpoint set -n "-[UIViewController viewDidLoad]" -C "po $arg1" -G1
Make sure to use a capitol -C, since LLDB’s -c performs a different option!
This says to create a breakpoint on -[UIViewController viewDidLoad], then execute the (C)ommand “po $arg1”, which prints out the instance of the UIViewController. From there, the -G1 option tells the breakpoint to automatically continue after executing the command.
Verify the console displays the expected information by triggering a viewDidLoad by tapping on one of the UITableViewCells containing a Unix signal.
Now, how can you send this to a coworker? In LLDB, type the following:
(lldb) breakpoint write -f /tmp/br.json
This will write all the breakpoints in your session to the /tmp/br.json file. You can specify a single breakpoint or list of breakpoints by breakpoint ID, but that’s for you to determine via the help documentation on your own time.
You can verify the breakpoint data either in Terminal or via LLDB by using the platform shell command to breakout into using Terminal.
Use the cat Terminal command to display the breakpoint data.
(lldb) platform shell cat /tmp/br.json
This means that you can send over this file to your coworker and have her open it via the breakpoint read command.
To simulate this, delete all breakpoints again.
(lldb) breakpoint delete
You will now have a clean debugging session with no breakpoints.
Now, re-import your custom breakpoint command:
(lldb) breakpoint read -f /tmp/br.json
Once again, if you were to trigger the UIViewController’s viewDidLoad method, the instance will be printed out due to your custom breakpoint logic! Using these commands, you can easily send and recieve LLDB breakpoint commands to help replicate a hard to catch bug!
Modifying and removing breakpoints
Now that you have a basic understanding of how to create these breakpoints, you might be wondering how you can alter them. What if you found the object you were interested in and wanted to delete the breakpoint, or temporarily disable it? What if you need to modify the breakpoint to perform a specific action next time it triggers?
First, you’ll need to discover how to uniquely identify a breakpoint or a group of breakpoints.
Build and run the app to get a clean LLDB session. Next, pause the debugger and type the following into the LLDB session:
(lldb) b main
The output will look something similar to the following:
Breakpoint 1: 70 locations.
This creates a breakpoint with 70 locations, matching the function "main" in various modules.
In this case, the breakpoint ID is 1, because it’s the first breakpoint you created in this session. To see details about this breakpoint you can use the breakpoint list subcommand.
Type the following:
(lldb) breakpoint list 1
The output will look similar to the truncated output below:
1: name = 'main', locations = 70, resolved = 70, hit count = 0
1.1: where = Signals`main at AppDelegate.swift, address = 0x00000001098b1520, resolved, hit count = 0
1.2: where = Foundation`-[NSThread main], address = 0x0000000109bfa9e3, resolved, hit count = 0
1.3: where = Foundation`-[NSBlockOperation main], address = 0x0000000109c077d6, resolved, hit count = 0
1.4: where = Foundation`-[NSFilesystemItemRemoveOperation main], address = 0x0000000109c40e99, resolved, hit count = 0
1.5: where = Foundation`-[NSFilesystemItemMoveOperation main], address = 0x0000000109c419ee, resolved, hit count = 0
1.6: where = Foundation`-[NSInvocationOperation main], address = 0x0000000109c6aee4, resolved, hit count = 0
1.7: where = Foundation`-[NSDirectoryTraversalOperation main], address = 0x0000000109caefa6, resolved, hit count = 0
1.8: where = Foundation`-[NSOperation main], address = 0x0000000109cfd5e3, resolved, hit count = 0
1.9: where = Foundation`-[_NSFileAccessAsynchronousProcessAssertionOperation main], address = 0x0000000109d55ca9, resolved, hit count = 0
1.10: where = UIKit`-[_UIFocusFastScrollingTest main], address = 0x000000010b216598, resolved, hit count = 0
1.11: where = UIKit`-[UIStatusBarServerThread main], address = 0x000000010b651e97, resolved, hit count = 0
1.12: where = UIKit`-[_UIDocumentActivityDownloadOperation main], address = 0x000000010b74f718, resolved, hit count = 0
This shows the details of that breakpoint, including all locations that include the word "main".
A cleaner way to view this is to type the following:
(lldb) breakpoint list 1 -b
This will give you output that is a little easier on the visual senses. If you have a breakpoint ID that encapsulates a lot of breakpoints, this brief flag is a good solution.
If you want to query all the breakpoints in your LLDB session, simply omit the ID like so:
(lldb) breakpoint list
You can also specify multiple breakpoint IDs and ranges:
(lldb) breakpoint list 1 3
(lldb) breakpoint list 1-3
Using breakpoint delete to delete all breakpoints is a bit heavy-handed. You can simply use the same ID pattern used in the breakpoint list command to delete a set.
You can delete a single breakpoint by specifying the ID like so:
(lldb) breakpoint delete 1
However, your breakpoint for "main" had 70 locations (maybe more or less depending on the iOS version). You can also delete a single location, like so:
(lldb) breakpoint delete 1.1
This would delete the first sub-breakpoint of breakpoint 1, which results in only one main function breakpoint removed while keeping the remaining main breakpoints active.
Where to go from here?
You’ve covered a lot in this chapter. Breakpoints are a big topic and mastering the art of quickly finding an item of interest is essential to becoming a debugging expert. You’ve also started exploring function searching using regular expressions. Now would be a great time to brush up on regular expression syntax, as you’ll be using lots of regular expressions in the rest of this book.
Check out https://docs.python.org/2/library/re.html to learn (or relearn) regular expressions. Try figuring out how to make a case-insensitive breakpoint query.
You’ve only begun to discover how the compiler generates functions in Objective-C and Swift. Try to figure out the syntax for stopping on Objective-C blocks or Swift closures. Once you’ve done that, try to design a breakpoint that only stops on Objective-C blocks within the Commons framework of the Signals project. These are regex skills you’ll need in the future to construct ever more complicated breakpoints.