How do expressions and printing work in LLDB (p, po, v), and how do you customise it with summaries, aliases and Python?
Predict first
LLDB is asked to run Python and to register a summary for Swift.Int. What shows?
// === main.swift ===
var total = 0
func add(_ n: Int) {
total += n
}
for i in 1...5 {
add(i)
}
print("total", total)
- 42 is printed by the script, and the summary is listed with its format string
- LLDB has no scripting, so it errors
- Only the script output appears; summaries cannot be added
Mental model. v reads the dashboard gauges without touching the engine. p and po reach in and run a small test on the live engine, so you can learn more but must remember you changed something.
LLDB has several ways to look at a value, and they differ in what they run. frame variable (v) reads variables straight from memory without executing code, so it is safe and fast and works even when the process state is fragile. p (expression) compiles and runs an expression in the paused process and prints the result with its type, using any type summaries you have; it can have side effects (expr total = 100 changes the running program, handy for testing a fix without rebuilding, dangerous if forgotten). po (expression -O) prints the object’s description: it calls debugDescription on Swift types that conform to CustomDebugStringConvertible (or the Objective-C debugDescription), so give your own types a good one and po becomes much more useful. When an expression fails to evaluate in a Swift frame, switch the language (expression -l objc -- …) or make sure the module is imported and debug info is available (an optimised build often shows <optimized out>). thread backtrace all shows every thread, image lookup finds a symbol or an address, and frame select moves up the stack.
LLDB is scriptable. Type summaries (type summary add) change how a type prints, so an opaque struct shows its meaningful fields, and synthetic children let you present a computed structure. Breakpoint commands run debugger commands when a breakpoint hits; command alias and command regex shorten long incantations; and the embedded Python interpreter (script, command script import) gives you the full API, as in the check below where script evaluates Python and a summary is registered. Put your favourites in ~/.lldbinit (or a project-level init file) so they load every session. This is a learning topic: you do not need scripts every day, but knowing that “print this type nicely” and “run this check at every hit” are a few lines away is what separates a quick diagnosis from a long one.
The embedded interpreter runs Python, and type summaries are registered per type and listed with their template.
Common traps
- Leaving a changed value after
exprand forgetting it. Your later observations are of a program you modified. - Relying on
poin an optimised build. Variables are often optimised out; debug with an unoptimised build, or log.
They might follow up with
- What is the difference between
pandpo?pprints a value with its type and summaries;poprints the object’s description. - How do you make
pouseful for your type? ImplementCustomDebugStringConvertiblewith the fields you care about.
Related questions
- 23.1.1 What kinds of breakpoints are there in Xcode and LLDB, and how do conditions, actions, symbolic and exception…
- 23.3.2 Why do some bugs appear only in Release builds, how do assert, precondition and fatalError differ, and how do…
- 23.2.1 How do you debug layout and views with the view debugger, and how do you inspect what your app really sends…
← What kinds of breakpoints are there in Xcode and LLDB, and how do…