Advanced Debugging with Xcode

Debugging is such an important aspect of any programmer’s life but we usually take it for granted. In this article, I am going to help you level up your debugging skills by exploring a little bit about LLDB and why it is important for us to know a little more than we usually know about it. I shall also talk about what Xcode offers us through their UI to make things easier for us.

The initial motivation to explore this topic was to help me improve my productivity. As a newbie, I had learned to use the following two tools and they were:

  1. Breakpoints — these magical things that could make my code stop executing so that I could observe what was going on in at that point of time in my code.
  2. The command po — to print the description of any object that I wanted to view to see what value it held.
That blue tag — Breakpoint and po index to show the value stored in the `index` variable.

This was the limit of my knowledge of what Xcode had to offer in terms of helping me debug my code, and although this was a huge leap from having to write print statements all over my code to visualize what was going on in my code, I knew there were more possibilities.

In this article, I shall highlight a few lldb commands that I feel will up your debugging skills. On top of that, I shall also highlight a few Xcode specific tools which can help you execute these lldb commands with ease so that you can make it part of your programming lifecycle.

The best way to explain these debugging techniques is by actually implementing them. For that reason, I’ve attached a sample project that you can download. The project has a view that takes in just a card number and displays the number of characters that have currently been inputted.

For the first run, disable all the breakpoints and run the code. You shall see some very evident UI bugs and a few (not so evident) logical bugs as well. If you want to find all the bugs I’ve highlighted them with a comment //BUG throughout the code. I shall discuss only a few fixes over here and you can always go through the code once you’ve learned the features Xcode provides us with, for better debugging.

Remember the purpose of the article is not to highlight the fixes rather it is to help you learn how to make fixing bugs easier.

expression

So let us start by learning a new lldb command here. It is the expression command. This command helps us execute lines of code while the code is held at a breakpoint. You can write any valid swift code (or Objective-C code for that matter) in there to execute it.

One of the biggest use cases of this functionality is that you can edit values of variables to make them simulate the cases that you want to test.

Example 1 — Getting our hands dirty

Introduction to po and breakpoints.

In the above image, you can see in the debug console that I have hit a breakpoint at line 28 and while the code execution was halted I changed the value of the variable finalColor from UIExtendedSRGBColorSpace 0.27451 0.172549 0.462745 0.5 to UIExtendedSRGBColorSpace 0.5 0 0.5 1

Example 2 — Changing UI

You can also change the UI during runtime by using the expression command.

Let us go through that example as well. Let us say that our view controller looked something like this initially.

Initial state
  1. Hit any breakpoint (in the project you can place one near line 70 and hit it by clicking on the start payment button) once your app is running and try to execute the following command in the console:
expression self.view.backgroundColor = UIColor.black

With this code execution, you might expect to see the view’s background color to change to black. Although for the changes to reflect on your simulator or device without actually restarting your code you will have to run another command

expression CATransaction.flush()

All this command does is that it forces CoreAnimation to flush any pending UI changes and you shall see your view’s backgroundColor change from white to black.

This is after running CATransaction.flush()
If you noticed closely we were able to inject code during the runtime to make changes without having to recompile our app.

Which brings me to my next topic which is how to use your breakpoints in a much better way.

Breakpoints++

You learned that you can inject code at run time, but having to hit a breakpoint and then write the code to be executed every time you pass through that line can turn out to be tedious in the long run. For this exact case, Xcode provides you with breakpoints that do much more than just halt the program execution.

Custom breakpoint

You can see a popover near the breakpoint at line 59. You can access this popover by first creating a breakpoint on that line and then right-clicking on it and then selecting Edit Breakpoint.

This popover has a few things that need to be discussed.

  1. Condition: To execute the breakpoint on if a particular condition is met.
  2. Ignore: Number of times the breakpoint will be ignored before getting triggered.
  3. Action: This section has a lot of options which can be selected but for now click on Add Action and we shall use Debugger Command. You can also log messages, run scripts, etc by selecting the appropriate option.

4. Automatically continue after evaluating actions: If checked, does not stop the code execution when the breakpoint is hit.

Let us assume the bug (on line 74 in the project) that needed to be fixed was that the Start Payment button had to be renamed to Restart Payment once the user has already seen the Card Details view (the purple view).

From the previous section, we learned that we could inject code using the expression command. Custom breakpoints make it much easier for us to inject code.

  • Create a breakpoint on line 74,
  • Right click on the breakpoint and click on Add Action
  • Add the following line of code as shown in the figure below.
expression self.startPaymentButton.setTitle("Restart Payment", for: .normal
  • Do not forget to check the Automatically continue after evaluating actions so that the code does not halt after injecting your fix.

Now as soon as you click on that button again, you will see that the button’s title has been set to Restart Payment.

This example shows that Breakpoints are not only useful for halting your code to visualise the current state but also to inject code to fix bugs on the fly.

Symbolic breakpoints

So up until now, we’ve discussed situations where you know where things are going wrong. You know exactly where to check and you put a custom breakpoint there, inject your fix and you’re good to go.

But what about cases when you do not know where things are going wrong, all you can see is that your app is not behaving as intended.

In that case, we have Symbolic Breakpoints to the rescue. You might have heard of these breakpoints mainly in cases where you want to debug when your constraints are breaking using the UIViewAlertForUnsatisfiableConstraints symbolic breakpoint.

In this section, I would like to show you that they do a lot more than just constraint debugging.

A symbolic breakpoint is a breakpoint that is hit whenever a symbol (or function) is called. You can create a symbolic breakpoint easily. Head over to the breakpoint navigator, in the bottom right you would see a + button. Click on that and then click on Symbolic Breakpoint

Bottom Left: Click on + and then on Symbolic Breakpoint to create one.

Once you’ve done that, you’ll see a popover asking you to fill in the name of the symbol.

Enter the symbol name.

In this popover, you can enter the name of the symbol which when executed will trigger your breakpoint. You can enter both Swift and Objective-C method names. The good thing here is that Xcode’s autocompletion will help you out figure out the name of the function that you mean. In case you don’t get auto-completion, once you click enter after entering the function name, Xcode will show you a list of all the functions with that name, and you can select which ones to put your breakpoint on.

Pointers on how to get the name of the function you are looking for — Check the respective library’s documentation!!

Example — UILABELs!!!

So let us go through a bug in the project

BUG: Fill up the entire card number text fields. You shall see that the label below Card Number Length gets update properly. You can fill up to 16 characters. The bug arises when you delete the card number. Even though you’ve deleted all the characters, you’ll see that the card number length still shows up as 1 which is not right!

Let me pretend that I do not know what code is currently running in the project. All I know is that a label is not being set properly, which means that the setText: method of a particular UILabel is not being called.

So let’s create a Symbolic Breakpoint!

Symbolic breakpoint setup for [UILabel setText:]

I’ve created a symbolic breakpoint with the function name as

[UILabel setText:]

which I found from Apple Documentation on UILabel.

No I try to replicate the bug and I see that every time I type in a character I get a screen like this:

Now there are three things that I’d like you to notice on this screen:

  1. This screen shows the assembly code during the execution of which the code was halted.
  2. On the left-hand side, you can see the Debug Navigator which shows us the stack trace of how this function was called. You can see that [UILabel setText:] was being called from inside the ViewController.updateCardNumber() function, so now we know that this is the function responsible for setting up the card length label.
  3. In the bottom right, in the console, I’ve printed a few variables.
With Symbolic breakpoints your stack trace and console are your best friends.

Here I would like to also introduce the concept of arguments when you encounter such a situation where you’re stuck in assembly code.

$arg1, $arg2 …. $argn (where n is an integer), represent all the arguments related to the assembly code stack.

$arg1 — represents the object on which the function was called

$arg2 — represents the function that is called. You will have to typecast is using the (SEL) type.

$arg3 … $argn — represents the parameters sent to that function. In the above code, I’ve printed $arg3 and the output is 1 which says that the actual function that was called was

[UILabel setText:@"1"]

In case, a function let us say takes two arguments, $arg3 will give you the first parameter and $arg4 will give you the second parameter sent to the function and so on.

Coming back to the execution, click on the Continue Execution button and you shall see that has been set on the label. Now the bug is probably that ViewController.updateCardNumber() is not getting called when the last character gets deleted, which means that there is a call for updateCardNumber which is missing.

In this case, we know where this needs to be, which is where the textFields are getting updated. Go to line number 126 and you shall see that in this block of code which starts from

if newText.count == 0 {

there is no mention of updateCardNumber .

So here you can put another custom breakpoint which injects this particular line of code.

expression self.updateCardNumber()

Disable your symbolic breakpoint, and try replicating the bug again and you shall see that the card length label is getting updated properly.

Symbolic breakpoints might not help you solve the bug instantly but with the help of the Debug Navigator and the stack trace you will be able to narrow down the place where the bug might be!

thread jump

Another lldb command that I would like to teach people over here is the thread jump command. This command can be used to skip a few lines of code that you think is not required to be run. For example, an if condition that is probably causing a bug or is something you would like to ignore for the time being.

A word of caution while using this command. This command basically plays with the instruction pointer and might cause your code to run into a weird state causing your application to crash.

In my experience, I had pretty good use cases for it but my code crashed and I could not work with it as seamlessly as I thought I could. Still, I would like to explain a little bit about this command if it helps anybody.

The syntax is pretty simple

thread jump --line 206
// where 206 represents the line number in your code.
(or)
thread jump --by 2
// where 2 represents the number of lines of code that need to be skipped.

In Xcode, you can use this command in two ways.

  1. Create a breakpoint where you’d like to skip the code and then edit it. Then add the jump code eg: thread jump --by 2 in the action section of the breakpoint.
  2. Or you can use the drag option provided by Xcode.
Drag the instruction pointer
Hamburger icon, the one with the three lines

In order to drag, once you’ve hit the breakpoint as shown in the figure, click on the hamburger icon shown in the image above and drag it to the line you want to execute.

Make sure you don’t drag it randomly to wherever you want. See to it that you drag it to a place where it makes logical sense. For example, dragging it to another method call might make your application crash.

Conclusion

In conclusion, I would like to mention a few things if you’re interested in learning more about LLDB to try and improve your debugging skills.

Visit their documentation to find out more.

There are various other commands like frame , info etc which can help you during debugging.

Xcode helps you with stack trace debugging and view debugging with extremely good UI tools which should help you save time while fixing bugs.

Another great jump that you can make while learning this is to create custom helper commands using python and LLDB . You can create functions which use LLDB commands to do things like change view frames on the go, change colors on the go, etc.

To explore this you can check out Facebook’s library Chisel which was created was this specific purpose.

I hope this article has helped you learn a little bit more about debugging in XCode and has ultimately been a productivity booster for you while solving bugs.

Few links which can help you understand this article much better

— Neel Bakshi, iOS Engineer, Headout


Passionate about Swift and iOS development? Join us!! We’re hiring!