Xcode and LLDB Advanced Debugging Tutorial: Part 3

Fady Derias
8 min readOct 14, 2018

--

In the first and second parts of this three parts tutorial, we’ve covered how to utilize Xcode breakpoints to manipulate an existing property value and inject a new line of code via expression statements. We’ve also explored watchpoints that are a special type of breakpoints.

I developed a demo project with several intentional bugs to elaborate on how to use different types of breakpoints alongside the LLDB to fix bugs in your project/application.

If you didn’t go through part 1 and part 2 of this tutorial, it’s crucial to check them before proceeding with this final part.

One last time, the golden rule of this tutorial:
You’re not to stop the compiler or re-run the application after running it for the very first time. You’re fixing the bugs at runtime.

Symbolic Breakpoints 🔶

How are we doing so far?

4. The left navigation bar label that indicates how many times the user did load posts is not being updated.

Here are the steps to reproduce the last bug you’re to deal with:

✦ Scroll to the top of the table view, and pull down to refresh.
✦ Scroll to the bottom of the table view to load new posts. [for 7 times 😉]
✦ The left label is not being updated for every time new posts are successfully retrieved.

It’s important to point out that the integer pageNumber property answers the question, how many times the user did load posts..? (i.e. the left label on the navigation bar should be updated by the value of the pageNumber property). We’re quite sure from the previous fixes that the pageNumber property is updated properly, hence the problem is with setting its value to the dedicated label on the navigation bar.

In such cases, symbolic breakpoints strike in. Think of symbolic breakpoints as if the debugger is playing treasure hunt and you’re providing it with clues to get to that treasure. In your case, that happens to be the piece of code that updates the left label on the navigation bar.

Let me show you how to do that.

Show the breakpoint navigator, and click on the + button on the bottom left corner. Select symbolic breakpoint.

Add the following symbol

[UILabel setText:]

Don’t check the “Automatically continue after evaluating actions” box.

What we’re simply doing here is informing the debugger that whenever the setText function of any UILabel is called, it should pause. Notice that after creating the symbolic breakpoint, a child has been added.

It’s a feedback from the debugger that it was able to resolve the created symbolic breakpoint to a specific location inside UIKitCore framework. In other cases, the debugger might resolve the symbolic breakpoint to multiple locations.

Now you’re all set, pull down to refresh the posts table view. As soon as you release, the debugger will pause, and you’ll be seeing something like this:

At this point, you’re looking at some assembly code of the UIKitCore framework and on the left side is the stack trace that did cause the debugger to pause. The next thing we want to do is to inspect the arguments passed into the Objective-C message the debugger did pause at. In the lldb console, type the following:

po $arg1

This does point out to the register that holds the first argument. We can clearly see that the receiver of that Objective-C message is a UILabel instance. The UILabel instance has a text value that refers to a post label. It’s not what we are interested in, but let’s proceed with the registers inspection.

In the lldb console, type the following:

po $arg2

The $arg2 does always refer to the selector of the Objective-C message. In some cases, the lldb doesn’t implicitly know the types of the arguments, and hence we need to do some typecasting.

In the lldb console, type the following:

po (SEL)$arg2

Now, we can clearly see the selector of the current Obj-c message.

In the lldb console, type the following:

po $arg3

The $arg3 does always refer to the first parameter passed into the method. In our case, that is the string that is passed to the setText method.

Continue the execution of the program. The debugger will pause again. Repeat the above steps and eventually, you’ll figure out that the objective-c message belongs to another label of a post in the table view. It’s quite nonsense to keep doing this over and over again till we reach the UILabel instance that we are interested in. Things can definitely be better.

One thing you can do is to set a condition for the symbolic breakpoint to pause the debugger upon the success/fulfilment of that condition. This can be checking on a boolean value or waiting for a specific state to be reached .. etc.

However, we’re going for a different approach.

One Shot!

Disable the symbolic breakpoint you’ve created.
Logically speaking, the left navigation bar label that indicates how many times the user did load posts is updated after the posts are successfully retrieved via the HTTP GET request. Navigate to the section with the pragma mark Networking. Place a breakpoint inside the success completion handler of loadPosts. It should be below:

Objective-C

[self.tableView reloadData];

Swift

self.tableView.reloadData()

This will assure that the symbolic breakpoint will get triggered only after the table view has been reloaded and all of its equivalent labels have been updated.

Don’t check the “Automatically continue after evaluating actions” box. Add the following debugger command action:

breakpoint set --one-shot true -name '-[UILabel setText:]'

🤨🧐🤔

Let’s break that command:

  1. breakpoint set --one-shot true does create a “one-shot” breakpoint. A one-shot breakpoint is a type of breakpoint that only exists till it’s triggered then it gets automatically deleted.
  2. -name ‘- [UILabel setText:]’ does set a symbolic name to the created one-shot breakpoint. It’s quite similar to the one you created in the last section.

Let me recap this part. Here’s what you did:

  1. Adding a breakpoint (A) in the success completion handler of the function that executes the posts GET request.
  2. Adding a debugger command action to create a symbolic breakpoint (B) similar to the one you created the last section. Its symbol is the UILabel setText function.
  3. Setting the symbolic breakpoint (B) you created to be a one-shot breakpoint. It’s guaranteed that the symbolic breakpoint will pause the debugger only once since a one-shot breakpoint gets deleted automatically after it has been triggered.
  4. Breakpoint (A) is located after reloading the table view so that the created symbolic breakpoint (B) doesn’t pause the debugger for any of the labels related to the table view.

Now pull down the table view to refresh. Here’s what you’ll get:

Objective-C
Swift

The debugger did pause at the breakpoint (A) and hence setting the one-shot symbolic breakpoint.

Continue the program execution.

You’re back to the assembly code of the UIKitCore framework.

Let’s inspect the Objective-C message of the symbolic breakpoint arguments.

In the lldb console, type the following:

po $arg1

WELL WELL WELL, looks like you finally found your treasure !! 🥇🏆🎉

Time to shift our sights to the stack trace. Step to point 1.

Objective-C
Swift

It led you to the piece of code that is updating the pageNumberLabel text. It’s quite obvious that the text is always set to a string with a format of integer value 0 rather than the pageNumber property. Let’s test it before we make actual changes to our code.

You’re an expert now 🧢

Add a breakpoint in a separate line below the marked line of code. Add the following debugger command action:

Objective-C

expression self.pageNumberLabel.text = [NSString stringWithFormat:@"Page %tu", self.pageNumber]

Swift

expression pageNumberLabel.text = String(format: "Page %tu", pageNumber)

Remove/Disable breakpoint(A), accordingly, this will disable breakpoint(B)

Now pull to refresh and scroll to load more posts. The left navigation bar label is being updated. 🎉

Mission Accomplished !! 💪 💪

You can now stop the compiler and add the fixes we discussed in your code.

Summary

In this tutorial, you’ve learned

  1. How to use breakpoints alongside debugger action expression statements to manipulate existing values/properties.
  2. How to use breakpoints alongside debugger action expression statements to inject lines of code.
  3. How to set watchpoints to certain properties to monitor their values when being updated.
  4. How to use symbolic breakpoints to pause the debugger based on defined symbols.
  5. How to use one-shot breakpoints.
  6. How to use one-shot breakpoints alongside symbolic breakpoint.

Happy Debugging!! 😊

Third-party tools

I’ve used the following third-party tools for the convenience of this tutorial

--

--