Chapters

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

10. Regex Commands
Written by Derek Selander

In the previous chapter, you learned about the command alias command as well as how to persist commands through the lldbinit file. Unfortunately, command alias has some limitations.

An alias created this way will work great if you’re trying to execute a static command, but usually you’d want to feed input into a command in order to get some useful output.

Where command alias falls short is it essentially replaces the alias with the actual command. What if you wanted to have input supplied into the middle of a command, such as a command to get the class of a given object instance, providing the object as an input?

Fortunately, there is an elegant solution to supplying input into a custom LLDB command using a 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.

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. 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 a much 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. Yay!

Executing complex logic

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.

Packed into this command are three separate lines of code:

  1. @import QuartzCore imports the QuartzCore framework into the debugger’s address space. This is required because the debugger won’t understand what code you’re executing until it’s declared. You’re about to execute code from the QuartzCore framework, so just in case it 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], will flush the CATransaction queue. Manipulating the UI in the debugger will normally mean the screen will not reflect any updates until the debugger resumes execution.

    However, this method will update the screen 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 is not allowed so 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!

Provided LLDB is still paused, execute this newly created tv command:

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

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 stream editor input 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 it 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, which honors the above requirements.

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

In Xcode, create a Symbolic breakpoint on Signals.MasterViewController.viewDidLoad() -> () (make sure to keep the spacing).

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]/'

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) cpo [UIDevice currentDevice]
<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: Command contents 'self' failed to match any regular expression in the 'getcls' regex command.

This is because there was no matching regex for the input you provided. Let’s add one which catches other forms of input to getcls. Type the following into LLDB now:

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

This looks a bit more complex, but it’s not too bad. The first part of the command is the same as you added before. But now you’ve added another regex to the end. This one is a catch-all, just like the rlook command you added. This catch-all simply calls type(of:) with the input as the parameter.

Try executing the command again for self:

(lldb) getcls self

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

(lldb) getcls self .title 

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.

Supplying multiple parameters

The final party trick you’ll explore in command regex is supplying multiple parameters to command regex. However, before you do that, revisit the first command:

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

Take a look at the (.+). The parentheses around this make it what is known as a capture group. The %1 in the right hand side of the substitution (the replacement) indicates that the %1 should be replaced with the first capture group. Therefore this whole regular expression means that the entire text is captured, and image lookup -rn is added before it.

By supplying more capture groups, you can add more parameters to parse.

In an earlier chapter, you explored the private statusBar property of the UIApplication instance.

You can look it up using Objective-C like so:

(lldb) ex -l objc -O -- [[UIApplication shared] statusBar]

But this is a private API meaning you can’t call it via Swift unless you bring the Objective-C runtime into play, or add statusBar method in a category in your own header.

To be able to execute this in the Swift LLDB context, you can execute the following:

(lldb) ex -l swift -O -- UIApplication.shared.perform(NSSelectorFromString("statusBar"))

This is a little much to type. You can use the command regex to create multiple capture groups for this command.

In LLDB, type the following:

(lldb) command regex swiftperfsel 's/(.+)\s+(\w+)/expression -l swift -O -- %1.perform(NSSelectorFromString("%2"))/'

And then give this command a whirl:

(lldb) swiftperfsel UIApplication.shared statusBar

You’ll get the Swift formatted output to this private property:

▿ Optional<Unmanaged<AnyObject>>
  ▿ some : Unmanaged<AnyObject>
    - _value : <UIStatusBar_Modern: 0x7fa86dc05820; frame = (0 0; 375 44); autoresize = W+BM; layer = <CALayer: 0x6000018e5000>>

As you can see, adding multiple parameters quickly ups the complexity of the regular expression. By combining multiple capture groups with multiple chained groups, you can make a rather versatile command regex to handle all types of optional and required input. However, that’s going to look really, really ugly since all of this needs to be declared on one line.

Fortunately, LLDB has the 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 4th section of this book.

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

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