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

10. Regex Commands
Written by Walter Tyree

In the previous chapter, you learned about command alias as well as how to persist commands through an lldbinit file. Unfortunately, command alias has some limitations because lldb essentially just replaces the alias with the actual command when it parses your input.

In this chapter, you’ll combine the input substitution technique from the last chapter with regular expressions to create more flexible custom lldb commands using command regex.

command regex

The lldb command command regex acts much like command alias, except you can provide a regular expression for input which will be parsed and applied to the action part of the command.

command regex takes an input syntax that looks similar to the following:

s/<regex>/<subst>/

This is a normal regular expression. It starts with ’s/’, which specifies a stream editor input to use the substitute command. The <regex> part is the bit that specifies what should be replaced. The <subst> part says what to replace it with.

Note: This syntax is derived from the sed Terminal command. This is important to know, because if you’re experimenting using advanced patterns, you can check the man pages of sed to see what’s possible within the substitute formatting syntax.

Time to look at a concrete example. Open up the Signals Xcode project. Build and run, then pause the application in the debugger. Once the lldb console is up and ready to receive input, enter the following command in lldb:

(lldb) command regex rlook 's/(.+)/image lookup -rn %1/'

This command you’ve entered will make your image regex searches much easier. You’ve created a new command called rlook. This new command takes everything after the rlook and prefixes it with image lookup -rn . It does this through a regex with a single matcher (the parentheses) which matches on one or more characters, and replaces the whole thing with image lookup -rn %1. The %1 specifies the contents of the matcher.

Note: There is a subtle, but important, difference in the matching behavior of the % replacement from the last chapter. In the previous chapter, each argument got matched up with a % placeholder and the rest of the input got appended to the end of the command. Now, everything that doesn’t match just gets ignored.

So, for example, if you enter this:

rlook FOO

lldb will actually execute the following:

image lookup -rn FOO

Now, instead of having to type the soul-crushingly long image lookup -rn, you can just type rlook!

But wait, it gets better. Provided there are no conflicts with the characters rl, you can simply use that instead. A feature of lldb is that you can specify any command, be it built-in or your own, by using any prefix which is not shared with another command.

This means you can easily search for methods like viewDidLoad using an even more convenient amount of typing. Try it out now:

(lldb) rl viewDidLoad

This will produce all the viewDidLoad implementations across all modules in the current executable. Try limiting it to only code in the Signals app:

(lldb) rl viewDidLoad Signals

Now that you’re satisfied with the command, add the following line of code to your ~/.lldbinit file:

command regex rlook 's/(.+)/image lookup -rn %1/'

Note: The best way to implement a regex command is to use lldb while a program is running. This lets you iterate on the command regex (by redeclaring it if you’re not happy with it) and test it out without having to relaunch lldb.

Once you’re happy with the command, add it to your ~/.lldbinit file so it will be available every time lldb starts up. Now the rlook command will be available to you from here on out, resulting in no more painful typing of the full image lookup -rn command.

Remember a couple chapters back when image lookup’s output was described as less than ideal? You can use the following command regex instead!

command regex rsearch 's/(.+)/script print("\n".join([hex(i.GetSymbol().GetStartAddress().GetLoadAddress(lldb.target)) + " " +i.GetSymbol().GetName() + "\n" for i in lldb.target.FindGlobalFunctions("%1", 0, lldb.eMatchTypeRegex)]))/'

This uses lldb’s script bridging (discussed in the “Custom LLDB Commands” Section) in combination with a regular expression command. Using this in the Signals project will produce a significantly cleaner display of information:

(lldb) rsearch viewDidLoad\]$
0x18d6ff604 -[UIDocumentBrowserViewController viewDidLoad]

0x18d70a944 -[DOCRemoteViewController viewDidLoad]

0x18d728e5c -[DOCTargetSelectionBrowserViewController viewDidLoad]

0x18417b500 -[_UIAlertControllerTextFieldViewController viewDidLoad]

0x1841a10b0 -[UIAlertController viewDidLoad]

0x1844bfb34 -[_UIProgressiveBlurContextController viewDidLoad]

0x1844f1fa4 -[UITabBarController viewDidLoad]
...

Executing Complex Logic

It still might be hard to see how this is a powerful improvement over just using command alias, well, time to take the command regex up a level! You can actually use this command to execute multiple commands for a single alias. While lldb is still paused, implement this new command:

(lldb) command regex -- tv 's/(.+)/expression -l objc -O -- @import QuartzCore; [%1 setHidden:!(BOOL)[%1 isHidden]]; (void)[CATransaction flush];/'

This complicated, yet useful command, will create a command named tv (toggle view), which toggles a UIView (or NSView) on or off while the debugger is paused. This can be quite helpful when you’re debugging a complex layout and want to confirm you’ve got a handle to the right UI element.

Packed into this command are three separate lines of code:

  1. @import QuartzCore imports the QuartzCore framework symbols into lldb. This allows lldb to call code in the debugged process. lldb uses modules in order to call symbols within a process (you’ll learn more about modules in the “Low Level” section). The code that toggles view visibility is found in the QuartzCore framework, so just in case QuartzCore hasn’t been imported yet, you’re doing it now.

  2. [%1 setHidden:!(BOOL)[%1 isHidden]]; toggles the view to either hidden or visible, depending what the previous state was. Note that isHidden doesn’t know the return type, so you need to cast it to an Objective-C BOOL.

  3. The final command, [CATransaction flush], flushes the CATransaction queue. Manipulating the UI in the debugger normally means the screen will not reflect any updates until the debugger resumes execution.

    However, this method updates the screen immediately resulting in lldb not needing to continue in order to show visual changes.

Note: Due to the limitations of the input params, specifying multiline input for command regex is not allowed. This means you have to join all the commands onto one line. This is ugly but necessary when crafting these regex commands. However, if you ever do this in actual Objective-C/Swift source code, may the Apple Gods punish you with extra-long app review times! :]

Pause the app if it’s running and execute this newly created tv command. Be mindful of the number of square brackets, lldb and auto-complete may conspire to mess everything up.

(lldb) tv [[[UIApp keyWindow] rootViewController] view]

Bring up the Simulator to verify the view has disappeared.

Now simply press Enter in the lldb console, as lldb will repeat the last command you’ve entered. The view will flash back to normal. Keep pressing Enter for a nice strobe effect, whee!

Now that you’re done implementing the tv command, add it to your ~/.lldbinit file:

command regex -- tv 's/(.+)/expression -l objc -O -- @import QuartzCore; [%1 setHidden:!(BOOL)[%1 isHidden]]; (void)[CATransaction flush];/'

Chaining Regex Inputs

There’s a reason why that weird sed stream editor input style was chosen for using this command: this format lets you easily specify multiple actions for the same command. When given multiple commands, the regex will try to match each input. If the input matches, that particular <subst> is applied to the command. If the input doesn’t match for a particular stream, it’ll go to the next command and see if the regex can match that input.

It’s generally necessary to use the Objective-C context when working with objects in memory and registers. Also, anything that begins with the square open bracket or the ‘@’ character is (likely) Objective-C. This is because Swift makes it difficult to work with memory, and Swift won’t let you access registers, nor do Swift expressions usually ever begin with an open bracket or ‘@’ character.

You can use this information to automatically detect which context you need to use for a given input.

Let’s see how you’d you go about building a command which gets the class information out of an object, and honors the following requirements:

  • In Objective-C, you’d use [objcObject class].
  • In Swift, you’d use type(of: swiftObject).

In the Signals project, create a GUI breakpoint on the first line of viewDidLoad() in the MainViewController. This will ensure that lldb pauses in a Swift context with the main views of the application loaded.

Build and run, then wait for the breakpoint to be triggered. As usual, head on over to the debugger.

First, build out the Objective-C implementation of this new command, getcls.

(lldb) command regex getcls 's/(([0-9]|\$|\@|\[).*)/cpo [%1 class]/'

Note: This command regex assumes you have the cpo alias from the last chapter in your ~/.lldbinit.

Wow, that regex makes the eyes blur. Time to break it down:

At first, there’s an inner grouping saying the following characters can be used to match the start:

  • [0-9] means the numbers from 0-9 can be used.
  • \$ means the literal character ‘$’ will be matched.
  • \@ means the literal character ‘@’ will be matched.
  • \[ means the literal character ‘[’ will be matched.

Any characters that start with the above will generate a match. Following that is .* which means zero or more characters will produce a match.

Overall, this means that a number, $, @, or [, followed by any characters will result in the command matching and running cpo [%1 class]. Once again, %1 is replaced with the first matcher from the regex. In this case, it’s the entire command. The inner matcher (matching a number, $, or so on) would be %2.

Try throwing a couple of commands at the getcls command to see how it works:

(lldb) getcls @"hello world"
__NSCFString

(lldb) getcls @[@"hello world"]
__NSSingleObjectArrayI

(lldb) getcls [UIDevice currentDevice]
UIDevice

(lldb) po UIDevice.current
<UIDevice: 0x60800002b520>

(lldb) getcls 0x60800002b520
UIDevice

Awesome!

However, this only handles references that make sense in the Objective-C context and that match your command. For example, try the following:

(lldb) getcls self

You’ll get an error:

error: getcls

This is because there was no matching regex for the input you provided.

Redefine getcls and add a regex which catches other forms of input. Type the following into lldb:

(lldb) command regex getcls 's/(([0-9]|\$|\@|\[).*)/cpo [%1 class]/' 's/(.+)/expression -l swift -O -- type(of: %1)/'

The first part of the command is the same as what you added previously, but now you’ve added another regex to the end. This one is a catch-all, just like the rlook command you added earlier. This catch-all simply calls type(of:) with the input as the parameter.

Try executing the command again for self while execution is stopped in the Swift context of MainViewController.swift:

(lldb) getcls self

You’ll now get the expected Signals.MainViewController output. Since you made the Swift context as a catch-all, you can use this command in interesting ways.

(lldb) getcls self .title

This provides you with the class for the title property of self.

Swift.Optional<Swift.String>

Notice the space in there, and it still works. This is because you told the Swift context to quite literally take anything except newlines.

Once, you’re done playing with this new and improved getcls command, be sure to add it to your ~/.lldbinit file.

And that’s it for lldb’s command regex! The next step up from a command regex will be lldb’s full blown script bridging interface — a fully featured Python implementation for creating advanced lldb commands to do your debugging bidding. You’ll take an in depth look at script bridging in the “Custom LLDB Commands” section of this book.

For now, simply use either command alias or command regex to suit your debugging needs.

Key Points

  • command alias uses exact matching of your inputs and basic argument substitution.
  • command regex allows for more powerful input matching and argument substitution.
  • Join complex, multi-line commands using ; to combine them into one command regex command.
  • Chain multiple regex patterns to provide multiple actions for different inputs to the same command.

Where to Go From Here?

Go back to the regex commands you’ve created in this chapter and add syntax and help help documentation.

You’ll thank yourself for this documentation about your command’s functionality, when it’s 11 PM on a Friday night and you just want to figure out your bleeping gosh darn bug.

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.