Unified lldb Print Command
LLDB has a pair of new aliases, v
and vo
, and this is of interest because of what the Xcode 10.2 beta release notes have to say:
The LLDB debugger has a new command alias,
v
, for the “frame variable” command to print variables in the current stack frame. Because it bypasses the expression evaluator,v
can be a lot faster and should be preferred overp
orpo
.
Most lldb users are used to po
, which means this addition, and this advice, is going to change people’s workflow. Before printing, you now might ask and answer yourself “Do I need vo
or po
”? This is another case where lldb seems to be designed to expose, rather than abstract, the implementation details of a debugger. This isn’t the right choice for the majority of debugger users.
Fortunately, a Python if
statement can give us a single unified print command that makes the right vo
or po
choice. At its essence, the logic is:
if not vo(expression):
po(expression)
Details, Code
In addition to providing a single unified print command, this blog post aims to teach a little lldb Python. Being able to make the debugger do whatever you need feels really good, and I’d love for everyone to feel that from time to time. You don’t need to know Python, – like JavaScript, anyone can write Python without knowing it. Internet searches and trial and error get you really far.
Here’s the top level code for the unified print command:
import lldb
@lldb.command("po")
def vo_else_po(debugger, expression, context, result, _):
frame = context.frame
if not vo(expression, frame, result):
po(expression, frame, result)
This will first try the expression as a variable, and otherwise fallback to full expression evaluation.
Anyone new to lldb’s Python API will have questions, like “what exactly is @lldb.command(...)
”. See the epilog for explanations of these API quirks.
But first, this code calls two helper commands, vo()
and po()
which we need to implement.
vo
in Python
The vo
command is an alias for frame variable -O
, and the matching Python function is GetValueForVariablePath
.
It’s fairly simple to implement vo
, call GetValueForVariablePath()
, handle any errors, and print the description. The code is:
def vo(expression, frame, result):
value = frame.GetValueForVariablePath(expression)
if value.error.fail:
return False
print(value.description, file=result)
return True
This function returns True
if the expression is the name of a variable, or a variable expression like foo.bar
. Otherwise it returns False
to indicate that vo
failed, and to proceed with po()
.
po
in Python
The po
command is an alias for expression -O
, and the equivalent Python function is EvaluateExpression
.
Similar to vo()
, the po()
function can be written by calling the EvaluateExpression()
, handling any errors, and printing the description. Code:
def po(expression, frame, result):
value = frame.EvaluateExpression(expression)
if value.error.fail:
result.SetError(value.error)
return False
print(value.description, file=result)
return True
Complete Command
Putting it together, here is the code to have a unified po
command. This will override the builtin po
alias, with a smarter version that uses vo
if possible, otherwise does po
.
from __future__ import print_function
import lldb
@lldb.command("po")
def vo_else_po(debugger, expression, context, result, _):
frame = context.frame
if not vo(expression, frame, result):
po(expression, frame, result)
def vo(expression, frame, result):
value = frame.GetValueForVariablePath(expression)
if value.error.fail:
return False
# Use rstrip because of extra unwanted newline.
print(value.description.rstrip(), file=result)
return True
def po(expression, frame, result):
value = frame.EvaluateExpression(expression)
if value.error.fail:
result.SetError(value.error)
return False
print(value.description, file=result)
return True
To use it, add the following to your ~/.lldbinit
:
command script import path/to/vo_else_po.py
swift_po
The behavior described in this blog post is available in swift_po: https://github.com/kastiglione/swift_po.
swift_po is a simple replacement for po
, it avoids memory leaks caused by po
, accepts object pointers, and automatically uses vo
as demonstrated here.
Epilog: Python LLDB Answers
@lldb.command("po")
– decorator that registers the Python function as an lldb command namedpo
(this overrides the builtinpo
alias)expression
– the command input entered by the user, in this case the string is either a variable name or code to evaluatecontext
(SBExecutionContext
) – convenience accessor for the currentframe
,thread
,process
, etcresult
(SBCommandReturnObject
) – status of an lldb command, such as whether it errored, and also stores the command’s output (seefile=result
)error
(SBError
) – although named “error”, this is much like aResult
type, it can be eithersuccess
orfail
(error
is always non nil)description
(SBValue.description
) – generates the object description of a value, this is what separatespo
fromp