9.
Persisting & Customizing Commands
Written by Derek Selander
As you’ve probably noticed in your development career, typing the same thing over and over really sucks. If you use a particular command that’s difficult to type, there’s no reason you should have to type the whole thing out. Just as you’ve learned when creating breakpoints using regular expressions, you’d go crazy typing out the full names of some of those Swift functions.
The same idea can be applied to any commands, settings, or code executed in LLDB. However, there’s two problems that haven’t been addressed until now: persisting your commands and creating shortcuts for them! Every time you run a new LLDB session, all your previous commands you’ve executed will vanish!
In this chapter, you’ll learn how to persist these choices through the .lldbinit file. By persisting your choices and making convenience commands for yourself, your debugging sessions will run much more smoothly and efficiently. This is also an important concept because from here on out, you’ll use the .lldbinit file on a regular basis.
Persisting… how?
Whenever LLDB is invoked, it searches several directories for special initialization files. If found, these files will be loaded into LLDB as soon as LLDB starts up but before LLDB has attached to the process (important to know if you’re trying to execute arbitrary code in the init file).
You can use these files to specify settings or create custom commands to do your debugging bidding.
LLDB searches for an initialization file in the following places:
-
~/.lldbinit-[context] where
[context]is Xcode, if you are debugging with Xcode, or lldb if you are using the command line incarnation of LLDB.For example, if you wanted commands that were only available in LLDB while debugging in the Terminal, you’d add content to
~/.lldbinit-lldb, while if you wanted to have commands only available to Xcode you’d use~/.lldbinit-Xcode. -
Next, LLDB searches for content found in ~/.lldbinit. This is the ideal file for most of your logic, since you want to use commands in both Xcode and terminal sessions of LLDB.
-
Finally, LLDB will search the directory where it was invoked. Unfortunately, when Xcode launches LLDB, it’ll launch LLDB at the
/root directory. This isn’t an ideal place to stick an.lldbinitfile, so this particular implementation will be ignored throughout the book.
Creating the .lldbinit file
In this section you’re going to create your first .lldbinit file.
First, open a Terminal window and type the following:
nano ~/.lldbinit
This uses the nano text editor to open up your .lldbinit file. If you already have an existing file in the location, nano will open up the file instead of creating a new one.
Note: You really should be using some form of vi or emacs for editing .lldbinit, and then angrily blog about how unconventional the other editor is. I’m suggesting
nanoto stay out of the great debate.
Once the file is open in the nano editor, add the following line of code to the end of your .lldbinit file:
command alias -- Yay_Autolayout expression -l objc -O -- [[[[[UIApplication sharedApplication] keyWindow] rootViewController] view] recursiveDescription]
You’ve just created an alias — a shortcut command for a longer expression. It’s named Yay_Autolayout and it’ll execute an expression command to get the root UIView (iOS only) and dump the position and layout of the root view and all of its subviews.
Save your work by pressing Ctrl + O, but don’t exit nano just yet.
Open the Signals Xcode project — you know, the one you’ve been playing with throughout this section. Build and run the Signals application. Once running, pause execution and type the alias in the debugger:
(lldb) Yay_Autolayout
This will dump out all the views in the applications! Neat!
Note: The cool thing about this command is it’ll work equally well for apps you do — and don’t — have source code for.
You could, hypothetically, attach LLDB to the Simulator’s SpringBoard and dump all the views using the exact same method.
Now, use LLDB to get help for this new command:
(lldb) help Yay_Autolayout
The output will look kinda meh. You can do better. Go back to the nano Terminal window and rewrite the command alias to include some helpful information, like so:
command alias -H "Yay_Autolayout will get the root view and recursively dump all the subviews and their frames" -h "Recursively dump views" -- Yay_Autolayout expression -l objc -O -- [[[[[UIApplication sharedApplication] keyWindow] rootViewController] view] recursiveDescription]
Make sure nano saves the file by pressing Ctrl + O. Next, build and run the Signals project.
Now when you stop the debugger and type help Yay_Autolayout, you’ll get help text at the bottom of the output. This is done with the -H command.
You can also get a brief summary by just typing help, which gives the -h description along with the rest of the commands.
This may seem a bit pointless now, but when you have many, many custom commands in your .lldbinit file, you’ll be thankful you provided documentation for yourself.
Command aliases with arguments
You’ve just created a standalone command alias that doesn’t require any arguments. However, you’ll often want to create aliases to which you can supply input.
Go back to the nano window in Terminal. Add the following at the bottom of the file:
command alias cpo expression -l objc -O --
You’ve just created a new command called cpo. The cpo command will do a normal po (print object), but it’ll use the Objective-C context instead. This is an ideal command to use when you’re in a Swift context, but want to use Objective-C to print out an address or register of something you know is a valid Objective-C object.
Save your work in nano, and jump over to the Signals project. Navigate to MasterViewController’s viewDidLoad and set a breakpoint at the top of the function. Build and run the application.
To best understand the importance of the cpo command, first get the reference to the MasterViewController.
(lldb) po self
You’ll get output similar to the following:
<Signals.MasterViewController: 0x7fc8295071a0>
Take the memory address you get at the end of the output (as usual, yours will likely be different), and try printing that in the debugger.
(lldb) po 0x7fc8295071a0
This will not produce any meaningful output, since you’ve stopped in a Swift file, and Swift is a type-safe language. Simply printing an address in Swift will not do anything. This is why the Objective-C context is so useful when debugging, especially when working in assembly where there are only references to memory addresses.
Now, use the new command you’ve just created on the address:
(lldb) cpo 0x7fc8295071a0
You’ll see the same output as you did with po self:
<Signals.MasterViewController: 0x7fc8295071a0>
This is a helpful command to get a NSObject’s description, whether it’s created with Objective-C or Swift.
Where to go from here?
You’ve learned how to create aliases for simple commands as well as persist them in the .lldbinit file. This will work across both Xcode and Terminal invocations of LLDB.
As an exercise, add help messages to your newly created cpo command in the ~/.lldbinit file so you’ll be able to remember how to use it when you have an onslaught of custom commands. Remember the -h option is the short help message that’s displayed when you just type help, while the -H option is the longer help command used when you type help command. Remember to use the -- to separate your help input arguments to the rest of your command.
In addition, write a command alias for something you often use. Put this alias in your ~/.lldbinit file and try it out!