If I ask which LLDB command you used the most? I am sure most iOS developers will answer po
. But did you know that you can actually define your own custom LLDB command using purely Swift code?
In this article, I would like to show you what it takes to create your own LLDB command. Here are what I will be covering:
- Adding your first LLDB command
- Adding LLDB command with arguments
- Converting complex Swift code to LLDB command
Without further ado, let’s get started!
Adding Your First LLDB Command
Understanding the LLDB Command Structure
In order to add a custom LLDB command, we must leverage the command alias
LLDB command. It has the following structure:
command alias [command_name] expr -l Swift -O -- [swift_code]
Let’s break down the above command in details:
command alias
: The LLDB command that alias the Swift code with a name[command_name]
: The custom command nameexpr -l Swift -O --
: Ask the LLDB debugger to interpret everything that follows as Swift code[swift_code]
: The Swift code that defines the custom command logic
For example, if we want to add a custom command named greet
that prints a “Hello World!” statement at the console, the LLDB command will be something like this:
command alias greet expr -l Swift -O -- print("Hello World!")
Adding the Custom Command
Now that we have constructed the alias command for greet
, it is time to add it to the LLDB debugger.
The most straightforward way to add the greet
command to the LLDB debugger is to execute the alias command in the Xcode console.
However, doing this only makes the greet
command available in that particular debugging session. In order words, we will need to retype the same alias command every time we start a new debugging session.
In order to avoid that from happening, we can leverage the .lldbinit
file located in the home directory. Note that it is a hidden file, if you can’t see the file, you can use the following shortcut to show the hidden files in your finder:
shift + command + .
Once you have enabled finder to show hidden files, but you still not able to locate the file, go ahead and create one in your home directory using the following terminal command:
touch ~/.lldbinit
After that, open the file and paste the entire alias command into the .lldbinit
file you just created. With that, Xcode will execute our alias command every time it starts a new debugging session.
Pro Tip:
If you do not want to restart your debugging session every time you have updated the
.lldbinit
file, you can reload it using the following command:
command source ~/.lldbinit
Adding LLDB Command with Arguments
In this section, let’s take things one step further by adding a command that is able to accept an argument. For demonstration purposes, let’s modify our greet
command so that it is able to accept a string and print out the greeting message accordingly.
This time, we will leverage the command regex
LLDB command. It has the following structure:
command regex [command_name] 's/[regex]/expr -l Swift -O -- [swift_code]/'
I won’t go into too much detail on how the regex command works, as it is beyond the scope of this article. Generally, all you need to do is to replace [regex]
with the regular expression statement (.+)
, and then use %1
to represent the argument in the Swift code.
With that in mind, we can update the greet
command accordingly like so:
command regex greet 's/(.+)/expr -l Swift -O -- print("Hello \(%1)!")/'
Here’s the greet
command in action (assuming name = "Swift Senpai"
):
(lldb) greet name
Hello Swift Senpai!
At this stage, you might ask: what if I need to pass in more than 1 argument? The answer is actually pretty simple.
First, append more (.+)
to the regex statement and separate each (.+)
with a whitespace. After that, use %2
, %3
, %4
… to represent each subsequence argument in the Swift code.
Now, let’s try to adjust our greet
command to accept 2 arguments:
command regex greet 's/(.+) (.+)/expr -l Swift -O -- print("Hello (%1) and (%2)!")/'
To use the command, just separate each argument with whitespace like so (assuming name1 = "Swift Senpai"
and name2 = "iOS developers"
):
(lldb) greet name1 name2
Hello Swift Senpai and iOS developers!
Now that you have seen how to add a custom LLDB command that takes multiple arguments. In the next section, I will show you how to convert a multi-line Swift function into a custom LLDB command.
Converting Complex Swift Code to LLDB Command
One caveat of adding custom Swift code as LLDB command is that everything must be done in one single line. Therefore, if we have a multi-line Swift function, we must first convert it into a single line, then only we can add it to the .lldbinit
file.
Let’s say we want to add the following Swift function that converts RGB value to hex value:
func hex(r: Int, g: Int, b: Int) {
/* Make sure RGB value within range */
if (r >= 0 && r <= 255) && (g >= 0 && g <= 255) && (b >= 0 && b <= 255) {
let rgb:Int = r<<16 | g<<8 | b<<0
let hex = String(format:"#%06x", rgb)
print(hex)
} else {
print("Invalid input value")
}
}
Notice how I use the /* */
and not the //
syntax for code comment, this is to ensure that our Swift code will not break after we convert it into a single line later on.
On top of that, we also need to make a few adjustments to our Swift code before we can convert it into a single line. Here are what we need to do:
- Define a variable for each function parameter.
- Assign
%1
,%2
,%3
… to each defined variable. - Add
;
at the end of each statement.
This is how our Swift code will looks like after the adjustments:
let r = %1;
let g = %2;
let b = %3;
/* Make sure RGB value within range */
if (r >= 0 && r <= 255) && (g >= 0 && g <= 255) && (b >= 0 && b <= 255) {
let rgb:Int = r<<16 | g<<8 | b<<0;
let hex = String(format:"#%06x", rgb);
print(hex);
} else {
print("Invalid input value");
}
With that, we can proceed to convert our Swift code into a single line. I personally like to use this free online tool for single-line conversion. However, if you know of any great tools out there that you would like to recommend, feel free to let me know.
Once we converted our Swift code into a single line, we can construct the regex command like so:
command regex hex 's/(.+) (.+) (.+)/expr -l Swift -O -- let r = %1; let g = %2; let b = %3; if (r >= 0 && r <= 255) && (g >= 0 && g <= 255) && (b >= 0 && b <= 255) { let rgb:Int = r<<16 | g<<8 | b<<0; let hex = String(format:"#%06x", rgb); print(hex); } else { print("Invalid input value"); }/'
Go ahead and paste the command into the .lldbinit
file, then we are good to go.
Practical Custom LLDB Commands
Now that you have learned how to add a custom command to your LLDB debugger, what custom LLDB command should you add then?
I personally find the following custom command especially useful. It is a command that pretty prints any JSON serializable type such as Dictionary
, Array
, Data
, etc as JSON string in the Xcode console. You can find out more here.
In addition, I also like the series of custom commands discussed in this article, where we can use them to modify the color of the UI elements on the fly without having to rebuild the project.
Wrapping Up
This article barely scratches the surface of the LLDB debugger capabilities. If you are new to LLDB, I hope this article can inspire you to start exploring this amazing debugging tool starting from today.
If you enjoy reading this article, feel free to check out my other articles related to testing and Xcode. You can also follow me on Twitter, and subscribe to my monthly newsletter.
Thanks for reading. 👨🏻💻
👋🏻 Hey!
While you’re still here, why not check out some of my favorite Mac tools on Setapp? They will definitely help improve your day-to-day productivity. Additionally, doing so will also help support my work.
- ✨ Bartender: Superpower your menu bar and take full control over your menu bar items.
- ✨ CleanShot X: The best screen capture app I’ve ever used.
- ✨ PixelSnap: Measure on-screen elements with ease and precision.
- ✨ iStat Menus: Track CPU, GPU, sensors, and more, all in one convenient tool.