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

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

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];/'
(lldb) tv [[[UIApp keyWindow] rootViewController] view]

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.

(lldb) command regex getcls 's/(([0-9]|\$|\@|\[).*)/cpo [%1 class]/'
(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
(lldb) getcls self
error: getcls
(lldb) command regex getcls 's/(([0-9]|\$|\@|\[).*)/cpo [%1 class]/' 's/(.+)/expression -l swift -O -- type(of: %1)/'
(lldb) getcls self
(lldb) getcls self .title
Swift.Optional<Swift.String>

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.

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