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

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

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.

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.

(lldb) image lookup -n "-[UIViewController viewDidLoad]"
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]
(lldb) image lookup -rn test

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.

@interface TestClass : NSObject
@property (nonatomic, strong) NSString *name;
@end
-[TestClass name]
-[TestClass setName:]
(lldb) image lookup -n "-[TestClass name]"
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) image lookup -n "-[TestClass setName:]"

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.

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"

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!
}
(lldb) image lookup -rn Signals.SwiftTestClass.name.setter
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
(lldb) b Signals.SwiftTestClass.name.setter : Swift.Optional<Swift.String>
% xcrun swift-demangle s7Signals14SwiftTestClassC4nameSSSgvs  # note the dollar sign is removed from function
$s7Signals14SwiftTestClassC4nameSSSgvs ---> Signals.SwiftTestClass.name.setter : Swift.String?
(lldb) image lookup -rn Signals.SwiftTestClass.name

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.

(lldb) b -[UIViewController viewDidLoad]
Breakpoint 1: where = UIKitCore`-[UIViewController viewDidLoad], address = 0x00000001845f0278

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.

(lldb) b Signals.SwiftTestClass.name.setter : Swift.Optional<Swift.String>
(lldb) rb SwiftTestClass.name.setter
(lldb) rb name\.setter
(lldb) rb '\-\[UIViewController\ '
(lldb) breakpoint delete
(lldb) rb '\-\[UIViewController(\ |\()'

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
(lldb) rb .
(lldb) rb . -s Commons
(lldb) rb . -s UIKitCore
(lldb) breakpoint delete
(lldb) rb . -s UIKitCore -o 1

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
(lldb) breakpoint set -A -p "if let"
(lldb) breakpoint set -p "if let" -f MainViewController.swift -f DetailViewController.swift
(lldb) breakpoint set -p "if let" -s Signals -A

Breakpoint Actions

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

(lldb) breakpoint delete
(lldb) breakpoint set -n "-[UIViewController viewDidLoad]" -C "po $arg1" -G1
(lldb) breakpoint write -f /tmp/br.json
(lldb) platform shell cat /tmp/br.json
(lldb) breakpoint delete
(lldb) breakpoint read -f /tmp/br.json

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?

(lldb) b main
Breakpoint 1: 105 locations.
(lldb) breakpoint list 1
(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
(lldb) breakpoint list 1 -b
(lldb) breakpoint list
(lldb) breakpoint list 1 3
(lldb) breakpoint list 1-3
(lldb) breakpoint delete 1
(lldb) breakpoint delete 1.1

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.

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