Chapters

Hide chapters

Advanced Apple Debugging & Reverse Engineering

Fourth Edition · iOS 16, macOS 13.3 · Swift 5.8, Python 3 · Xcode 14

Section I: Beginning LLDB Commands

Section 1: 10 chapters
Show chapters Hide chapters

Section IV: Custom LLDB Commands

Section 4: 8 chapters
Show chapters Hide chapters

4. Stopping in Code
Written by Walter Tyree

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 an Xcode project called Signals which you’ll find in the resources bundle for this chapter.

Open up the Signals starter project using Xcode. Signals is a basic primary-detail project themed as an American football app that displays some rather nerdily-named offensive play calls.

The app monitors several Unix signals, handles them when received and displays them in a list.

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.

Signals 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 bash running in Terminal.

In addition, there’s a UISwitch that toggles the signal handling. When the switch is toggled, it calls a C function sigprocmask to enable or disable the signal handlers that the Signals app is listening to.

Finally, the Signals 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.

Build and run the app on your preferred iOS Simulator running iOS 16 or greater. Once the Signals project is running, navigate to the Xcode console and pause the debugger.

Resume Xcode by clicking the same button you used to pause 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 symbol within your application. An example of a symbol is -[NSObject init], which refers to the init method of NSObject instances.

Symbolic breakpoints allow you to set a breakpoint on a symbol instead of a line of source code, allowing you to put breakpoints on Apple’s code as well as your own. Once you create a symbolic breakpoint, you don’t have to recreate it the next time the program launches.

You’re now going to set a symbolic breakpoint to show all the instances of NSObject being created.

Kill the app if it’s currently running.

  1. Switch to the Breakpoint Navigator.
  2. In the bottom left, click the plus button to display the contextual menu.
  3. 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 app into the console pane of Xcode…which, upon viewing, is quite a lot. In fact, it’s so many that my app sometimes crashed before it was done displaying class names.

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 Register Calling Convention”, but for now, simply know $arg1 is synonymous to the register that’s used for the first argument of a function call. Depending on your computer, this could be $rdi for x86_64 (Intel) machines, or $x0 for the newer ARM64 (Apple Silicon) machines.

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. Be mindful though, some older frameworks use exceptions during their normal operation and your breakpoint will get hit before it gets to your error. When this happens, just resume Xcode and eventually your real error will trigger the breakpoint.

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. Swift throws errors using a different mechanism than C++ or Objective-C which is why you can’t just use exception breakpoints in Swift.

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. There are other arguments that tell LLDB to look up by offset or line number and more.

The output will be similar to below:

1 match found in /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/PrivateFrameworks/UIKitCore.framework/UIKitCore:
        Address: UIKitCore[0x00000000004b9278] (UIKitCore.__TEXT.__text + 4943316)
        Summary: UIKitCore`-[UIViewController viewDidLoad]

You can tell this is a relatively new iOS version due to the location of the -[UIViewController viewDidLoad] method. Prior to iOS 12, this method was located in UIKit, but has now since moved to UIKitCore likely due to the macOS/iOS unification process.

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, this command will spit out the results. Because it is a regex lookup it also returns results when "test" is a part of a longer word like "latest" or "datestamp".

Note: Use the -n argument when you want exact matches (with quotes around your query if it contains spaces) and use the -rn arguments to do a regex search. The -n only command helps figure out the exact query to match a breakpoint which makes it unwieldy when dealing with long symbol names (which frequently occur in Swift). The -rn argument 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 naming conventions when code is generated by the compiler. This is known as name mangling.

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:

-[TestClass name]

…while the setter looks like this:

-[TestClass setName:]

Build and run the Signals app if it isn’t already running, then pause the program. 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/lolz/Library/Developer/Xcode/DerivedData/Signals-exknwuyeumkttfanwxtsssaetltk/Build/Products/Debug-iphonesimulator/Signals.app/Signals:
        Address: Signals[0x0000000100002354] (Signals.__TEXT.__text + 0)
        Summary: Signals`-[TestClass name] at TestClass.h:34

lldb dumps information about the function included in the executable. The output may look scary, but there are some good tidbits here.

Note: The image lookup command can produce a lot of output that can be pretty hard on the eyes when a query matches a lot of code. In a later chapter, you’ll build a cleaner alternative to lldb’s image lookup command 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.app/Signals executable, at an offset of 0x0000000100002354 in the __TEXT segment of the __text section of the file (don’t worry if that didn’t make sense, this is a concept called Mach-O and will be explained in the “Low Level” section of this book). lldb was also able to tell that this method was declared on line 34 of 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. Because the compiler created these methods at runtime, they don’t actually exist at line 34 of TestClass.h until the code is run.

Stop the Signals app if it’s running and open TestClass.h. Next, add a breakpoint to line 34 by clicking on the line number. Now open the Breakpoint Navigator. Notice that there is a single breakpoint set.

Run the app and Xcode adds two breakpoints: one for the synthesized setter and getter of the name property.

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. At compile time, though, dot notation is converted to standard syntax.

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 compiler does not generate a separate -[TestClass .name] setter. 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 Signals is running and paused in lldb. Feel free to clear the lldb console by pressing Command-K or clicking the trashcan icon 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/lolz/Library/Developer/Xcode/DerivedData/Signals-exknwuyeumkttfanwxtsssaetltk/Build/Products/Debug-iphonesimulator/Signals.app/Signals:
        Address: Signals[0x000000010000b0ec] (Signals.__TEXT.__text + 36248)
        Summary: Signals`Signals.SwiftTestClass.name.setter : Swift.Optional<Swift.String> at SwiftTestClass.swift:34

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>

This symbol is actually a pretty, unmangled representation of the actual function name, $s7Signals14SwiftTestClassC4nameSSSgvs. lldb will hide the unmangled names by default, but you can view them by adding the --verbose option, or just -v, in the image lookup command.

You can verify these two functions are the same with the following Terminal command:

% xcrun swift-demangle s7Signals14SwiftTestClassC4nameSSSgvs  # note the dollar sign is removed from function
$s7Signals14SwiftTestClassC4nameSSSgvs ---> Signals.SwiftTestClass.name.setter : 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 output. You’ll find the output matches the getter, (Signals.SwiftTestClass.name.getter) the setter, (Signals.SwiftTestClass.name.setter), as well as some helper methods for Swift constructors and some generated methods for using key paths.

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 that 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 click 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:]). As demonstrated above, 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 = 0x00000001845f0278

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. If lldb complains that it’s unable “to resolve breakpoint to any actual locations”, check the spelling and capitalization of your symbol.

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, b is an abbreviation for another, longer lldb command. Run the help with the b command to figure out the actual command yourself and learn all the cool tricks b can 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

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 expand 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 everything 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. This command will create breakpoints for every class, including classes in imported libraries, that contains a “name” property that has a setter. You’ll look at filtering symbols to match specific images and namespaces later in this chapter.

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\ '

lldb should respond that it has set a breakpoint in about 800 or so locations.

The back slashes are regex 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(\ |\()'

This regular expression is tweaked so either a space or an open parenthesis follows the symbol name. lldb should respond with more locations than before. Type breakpoint list to confirm that a few of the matches contain categories of UIViewController if you like.

Regex breakpoints let you capture a wide variety of breakpoints with a single expression.

Breakpoint Scope

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 UIKitCore methods in iOS 16.0, so this command should set breakpoints in over 160,000 locations. 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 a one-shot breakpoint gets 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

Be patient while your computer executes this command, as lldb has to set your breakpoint in those 160,000+ locations. 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. Pause the app and type breakpoint list to confirm that No breakpoints currently set.

Note: lldb is actually modifying memory when creating many breakpoints with this approach. That is, lldb will determine the address for the symbol loaded into memory, create an assembly instruction where control will stop letting lldb (or more precisely, debugserver) “catch” the program. An explanation of these internals can be found in Jonathan Levin’s Make Debugging Great Again article.

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 project, 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 MainViewController.swift and DetailViewController.swift, you could do the following:

(lldb) breakpoint set -p "if let" -f MainViewController.swift -f DetailViewController.swift

Notice how the -A has gone, and how each -f will let you specify a single 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).

Breakpoint Actions

You can also perform actions when suspended on a breakpoint in lldb just like using Xcode’s Symbolic Breakpoint window.

You will make a breakpoint which prints the UIViewController instance whenever viewDidLoad gets hit, but you’ll do it via lldb console. Then, you’ll export this breakpoint to a file so you can show how cool you are to your co-workers by using the breakpoint read and breakpoint write commands to share your breakpoint with them!

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 capital -C, since lldb’s -c performs a different option!

This command creates a breakpoint on -[UIViewController viewDidLoad], then executes 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? Pause the Signals app again and in lldb, type the following:

(lldb) breakpoint write -f /tmp/br.json

This will write all the breakpoints in your session, as well as any from the Breakpoint navigator, 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 execute terminal commands without leaving lldb.

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 receive 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 Signals app to get a clean LLDB session. Next, pause the debugger and type the following into lldb:

(lldb) b main

The output will look something similar to the following:

Breakpoint 1: 105 locations.

This creates a breakpoint with 105 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. If you had set any breakpoints in the Breakpoint navigator, they would already be created, so your ID would be greater than 1. 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:

(lldb) br list
Current breakpoints:
1: name = 'main', locations = 105, resolved = 105, hit count = 0
  1.1: where = Signals`main at AppDelegate.swift, address = 0x000000010070122c, resolved, hit count = 0
  1.2: where = Foundation`-[NSDirectoryTraversalOperation main], address = 0x0000000180758eb8, resolved, hit count = 0
  1.3: where = Foundation`-[NSFilesystemItemRemoveOperation main], address = 0x000000018075a6a8, resolved, hit count = 0
  1.4: where = Foundation`-[NSFilesystemItemMoveOperation main], address = 0x000000018075ac80, resolved, hit count = 0
  1.5: where = Foundation`-[NSOperation main], address = 0x00000001807d4554, resolved, hit count = 0
  1.6: where = Foundation`-[NSBlockOperation main], address = 0x00000001807d571c, resolved, hit count = 0
  1.7: where = Foundation`-[NSInvocationOperation main], address = 0x00000001807d5cbc, resolved, hit count = 0
  1.8: where = Foundation`-[_NSBarrierOperation main], address = 0x00000001807d5fe4, 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 105 locations (maybe more or less depending on the iOS version). You can also delete a single location:

(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.

Key Points

  • The Breakpoint Navigator in Xcode is a wrapper around many of the console breakpoint commands.
  • Symbolic Breakpoints can be set on symbols in your app or in any loaded library.
  • Use image lookup with the -n or -rn switches to find out where a symbol is defined.
  • The compiler synthesizes getters and setters for properties at runtime, so sometimes you have to launch an app before you can set breakpoints.
  • rbreak is an abbreviated command for breakpoint set -r that lets you use regular expressions to match symbol names to breakpoint on.
  • The -s and -f switches on breakpoint set allow you to constrain how many locations are included in a breakpoint.
  • The -p switch allows you to set a breakpoint on an expression in your source code.
  • The read and write subcommands of breakpoint allow you to export and import breakpoints into .json files for sharing or saving.
  • The breakpoint list and breakpoint delete commands take breakpoint ID numbers or ranges to constrain their actions.

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.

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.

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.
© 2026 Kodeco Inc.