Debugging In Xcode With LLDB
A Dataful Adventure
Mistakes Were Made
Sometimes we make assumptions about things that aren’t true. That statement may seem obvious, and it is, it’s human nature. When writing software, it’s always good to question whether your assumptions are accurate. Many times you’ll find that on closer examination, things aren’t exactly as they first appeared.
🤔 Challenge Your Assumptions
When using Xcode, one of the ways we can question our assumptions is by using the debugger. A debugger is a tool that allows you inspect and manipulate your code execution with fine-grained control. Through debugging, you can gain a greater understanding of what your code is doing.
This post aims to give a brief overview of debugging in Xcode as well some of the tools that are available to help you. I intend to write a series of posts on the topic of debugging, and this is the first one. The subject of debugging is vast, and this post does not aim to provide a full overview. If there is something that you feel missing stay tuned for more posts in this series. Also, feel free to leave me a comment.
Crash Land Journeys
This might sound a bit strange, but for a second think of your code crashing as a journey. It starts off when you hit run, and it compiles. Along the way, it goes on methods and meets many properties. Ultimately, it succeeds in finding crash land. When you start debugging you only have a small piece of the picture. You have the beginning and destination but to know how you got there you need to retrace the steps of the journey. To help you along your trip, Apple has bestowed upon you a valuable item, the LLDB debugger.
The Console and LLDB
LLDB is a software debugger that Apple provides as the standard debugging tool in Xcode. LLDB is a powerful tool for breaking down applications into their components pieces. Its uses aren’t limited to the Xcode IDE, you can use LLDB to break down and examine applications even if you do not have access to their source code. Given the breadth of capabilities and variety uses, this post is meant more like a cursory overview, and I don’t want to get too out of scope by exploring these in detail — yet.
With LLDB we are able to inspect the call stacks as well as specific stack frames within the stacks. A call stack is a data structure that is created by your application to keep track of all its behavior.
The Data Structure
A stack data structure is simply a queue in which the last element added is the first element returned out of it (LIFO queue — Last In First Out.) This is much the same as in the physical world in that you would take a brick from the top of a stack of bricks (if you took from the bottom — i.e. the first brick added — it would all come crashing down.)
Within call stacks are structures called stack frames. Stack frames contain information regarding the execution for which it was created. A lot of this information can become useful when you are debugging. The data ranges from the local variables to the memory address where it returns to upon completion.
While a program is running, it stores information about what it’s doing in a data structure known as a call stack. Each time a method is called, the program pushes a new stack frame on top of the call stack, which contains the following: the arguments passed to the method, if any, the local variables of the method, if any, and the address to return to after the method call finishes.
If you aren’t familiar with code breakpoints, allow me to give you a brief an incomplete introduction. In Xcode, breakpoints allow you interrupt the execution of your code at a particular point. This is useful in that it lets you examine the state of the application at a given moment in great detail.
Before we go further into breakpoints, let’s go over symbolication. Symbolication is the mapping memory addresses to more simple function names.
Back To Our Previously Scheduled Programming
When dealing with breakpoints, it’s important to understand that there is more than one type. Let’s briefly touch on some important types:
Xcode Line Breaks— The basic type of breakpoints and the easiest to use are the type that you can add in the side of the Xcode editor. This breakpoint will interrupt your code when that line is executed. This is perfect for situations where you have a pretty good idea of where things are going wrong.
Exception — Exception breakpoints are also commonly used type. Exception breakpoints interrupt your code when Xcode throws an exception rather than the error. If you’re not sure where things are going wrong, this is a great tool to see what happened immediately before crashing.
Conditional — Another way to use breakpoint is set conditions at which the application will interrupt its execution. This could be something as simple as a nil check for a property. This can be very helpful when you aren’t sure where a given value is coming from in your code.
Symbolic — Finally, there are symbolic breakpoints. These are slightly more tricky. A symbolic breakpoint is sort of like a predicate for filtering when to interrupt your code based on functionality. For instance, you can have a symbolic breakpoint for when an object is instantiated.
While there are multiple ways to go about implementing them, at their core, breakpoints are a simple concept. Apple has a pretty good definition for breakpoints:
A breakpoint is a mechanism to pause an app during execution at a predetermined location in order to inspect the state of variables and the app’s control flow. There are several different kinds of breakpoints, and they can be edited to add conditions, actions, and scripts. Although you can use the debugger to pause an app at any time, it’s helpful to set breakpoints before and even while your app is running so that you can pause it at known points where you have determined problems might be occuring.
One final aspect of debugging that I want to touch on in the post is what you can do to explore your code after you hit a breakpoint. You could say that there are a few steps you can take.
Stepping Over — When you ‘step over’ Xcode will run the current statement and stop at the next statement. If the statement is a function, it will execute in its entirety before stopping.
Stepping Into — When you step into Xcode will execute the current statement, however if that is a function Xcode will step into the execution of that function and then pause.
This is not my final word on the topic. There is so much more we can explore in this, I wouldn’t even call this post a complete introduction. This post is an introduction to the introduction. As I touched on earlier, LLDB allows us to explore worlds that would otherwise remain hidden and take apart code that we do not have access to. That’s where the fun starts!