LLDB debugger in Xcode — 2
Step through the code & inspect values
From the previous article, we have learnt how to set breakpoints. Once they are set, the app will pause at the breakpoints accordingly. In this article, we are going to explore the ways to interact with the app runtime at the paused breakpoints.
Step through the code
When app stops at breakpoints, we can use this little toolbar shown above in Xcode to step through the code. Let’s go through each of them to see what they can do.
Continue
This is to continue execution of all threads in the current process. The equivalent lldb command is:
(lldb) continue
Step over
Step over means to run the app to next point that can be based on source code or instruction.
If we want to step over to next point based on the source code, simply click step over button, or use lldb commands below:
(lldb) thread step-over
# Or its abbreviation form
(lldb) n
(lldb) next
If we want to step over to next point based on the instruction, click step over button while holding Control key or use lldb command below:
(lldb) thread step-inst-over
# Or its abbreviation form
(lldb) ni
(lldb) nexti
Also, we have the option to choose to run the current thread only while still pausing other threads. On the UI, click this button while holding Control & Shift key, it will step over only on the current thread while other threads still are paused, which could be quite useful in debugging thread issues. The equivalent lldb command option is -m <run-mode> that can be used like below:
(lldb) thread step-over -m this-thread
(lldb) n -m this-thread
(lldb) next -m this-thread
(lldb) thread step-inst-over -m this-thread
(lldb) ni -m this-thread
(lldb) nexti -m this-thread
Step in
Step in means stepping into calls, which, like step over can also happens on source code level or instruction level and have the option to run only the current thread.
All the operations for Step in are similar to Step over in Xcode. We just need to choose Step in button for this case. The corresponding lldb commands are:
# Source code level stepping in
(lldb) thread step-in
(lldb) s
(lldb) step
# Instruction level stepping in
(lldb) thread step-inst
(lldb) si
(lldb) stepi
# This thread only option
(lldb) thread step-in -m this-thread
(lldb) s -m this-thread
(lldb) step -m this-thread
(lldb) thread step-inst-in -m this-thread
(lldb) si -m this-thread
(lldb) stepi -m this-thread
Step out
Step out means finishing executing the current stack frame. Basically, when the breakpoint is at the beginning of the call, it will step in and it will stop at the returned point of the next call stack.
(lldb) thread step-out
(lldb) finish
Other useful lldb stepping commands
Some lldb stepping commands are not available through Xcode UI, but helpful in many cases. For example:
# Stepping to the line number in the same function
(lldb) thread until 8
# Early return from breakpiont of the current stack without finishing the flow
(lldb) thread return
Examine stack’s frames
When Xcode pauses the app at breakpoints, the debug navigator will show the stack frames like below:
We can get the similar information using the lldb commands below
(lldb) thread list
# And
(lldb) thread backtrace
At the same time, the variable viewer window will show the variables available in the current execution context.
The following lldb command will give us similar result:
(lldb) frame varibale
# Using -F to see a flattened view of the values
(lldb) frame variable -F
Please also note, we can select other stacks in debug navigator and the variable viewer will switch to show the values of the variables in the newly selected stack frame. We can achieve the same using lldb command below:
# Select stack frames using their index number to be the current frame
(lldb) frame select 1
Now, we know how to view Stack Frames in Xcode UI or using the corresponding lldb commands, which give us the whole picture of the app run time. It’s time to focus on the point of the interest that can help debug further.
You might be familiar with po command already, or even have used p and v commands as well. The main differences among them are:
Sourced from this WWDC session
PO
po is the short form of dwim-print -O -- . It prints out the description of the objects. For example, for a Swift type A below, as it implements CustomDebugStringConvertible protocol, po will print value of property debugDescription . If A doesn’t conform to CustomStringConvertible , po will print out value of description from its implementation of CustomStringConvertible protocol. If both protocols are not implemented, po will just print its type name with the object descriptions of its stored properties.
struct A: CustomDebugStringConvertible, CustomStringConvertible {
var description: String {
"Description of struct type A"
}
var debugDescription: String {
"Debug description of struct type A"
}
}
Under the hood, the lldb po work the way below:
Sourced from this WWDC session
P
p is a short form for dwim-print -- . It print the value of the variable using data formatter.
struct B {
let intValue: Int
let stringValue: String
}
let b = B(intValue: 5, stringValue: "A string")
With the above Swift struct value b of B type , p will print out the following using the default data formatters of Int and String type.
(Sample.B) (intValue = 5, stringValue = “A string”)
Please note that we can define our own type formatter, which will be covered in the future articles of this series.
Under the hood, the lldb po work the way below:
Sourced from this WWDC session
V
v is the short form of frame variable . Like p , it uses data formatter to format the output. Unlike p & po that create compilable code, compile it & execute it to get result, v command read values directly from the memory and feed them to data formatters like below:
Sourced from this WWDC session
Because v performs dynamic type resolution at each step of the interpretation, v is able to understand types better than p command.
Conclusion
In this article, we have explored ways to step through the code and use different command to display variable values to help under the runtime of the code. Hope this will help you debug more efficiently. If still struggling with some tricky bugs, LLDB has more commands to help, which will be covered in the future articles.
Sharing is caring!