Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Break on unsatisfiable constraints and send to wtfautolayout
command script import ~/.lldbscripts/break_unsatisfiable.py
import lldb
# Called when the Python module is imported into lldb
def __lldb_init_module(debugger, py_context):
# Create an lldb command, `open_wtfautolayout` that will call the Python
# function `open_wtfautolayout()`, below.
debugger.HandleCommand("command script add "
"-f break_unsatisfiable.open_wtfautolayout "
"open_wtfautolayout")
# Create a breakpoint on the UIViewAlertForUnsatisfiableConstraints()
debugger.HandleCommand("breakpoint set "
"--name UIViewAlertForUnsatisfiableConstraints "
"--language objc "
"--breakpoint-name wtfautolayout")
# Either: add the command open_wtfautolayout to the just-created breakpoint
# Notes: This causes some kind of communication failure between an
# lldb-rpc-server and Xcode; the running app hangs and there is no output
# in Xcode's console until the lldb-rpc-server process is killed.
# It works perfectly in lldb run from the command line, however.
# The open_wtfautolayout command itself also works if called directly, and
# *if added to a breakpoint created in Xcode's GUI*. It's not clear what
# the difference is.
# debugger.HandleCommand("breakpoint command add "
# "--one-liner open_wtfautolayout")
# debugger.HandleCommand("breakpoint command add "
# "--one-liner continue")
# Or: add the Python function `break_handler()` to the just-created
# breakpoint as a "script"; the signature is different than the signature of
# an lldb command
# Notes: This works correctly in Xcode, but I haven't figured out how to
# get the values of the function arguments.
debugger.HandleCommand("breakpoint command add --script-type python "
"--python-function break_unsatisfiable.break_handler")
#MARK:- Script
def break_handler(frame, location, py_context):
"""
When breaking in a stack frame for
UIViewAlertForUnsatisfiableConstraints, opens the constraint error log in
www.wtfautolayout.com
"""
# The frame argument *should* provide access to in-scope values, but because
# we are stopped in a function compiled without debug information, there
# doesn't seem to be a simple way.
# Various attempts:
arguments = frame.GetVariables(True, False, False, True)
print(map(lambda arg: arg.GetValue(), arguments))
# %rsi assumes running on simulator; see `get_arch()` below
constraint_log = eval_nsstring_description(frame, "%rsi")
if not constraint_log:
print("Could not get [%rsi description].")
else:
print(constraint_log)
constraint_log_address = get_rsi_value(frame)
constraint_log = eval_nsstring_description(frame, constraint_log_address)
if not constraint_log:
print("Could not evaluate [%rsi] as a string")
else:
print(constraint_log)
# N.B.: Return False to continue from the breakpoint automatically
return False
def get_rsi_value(frame):
"""Given a frame, get the value in the rsi register"""
for registers in frame.GetRegisters():
if "general purpose" in registers.GetName().lower():
rsi = registers.GetChildMemberWithName("rsi").GetValue()
if not rsi:
print("rsi not found")
return ""
# The value seems to be a string like "(unsigned long long)rsi = 0xfeedface"
return rsi.split("= ")[-1]
def eval_nsstring_description(frame, reference):
expression = "po [(NSString *){} description]".format(reference)
result = frame.EvaluateExpression(expression)
return result.GetValue()
# One possibility for getting the function's second argument may be to figure out
# which register (based on architecture) it's in and use that, because the
# non-debug-info `Frame` does seem to provide (some) access to the registers.
def get_arch(frame):
"""
Given a frame, follow the object graph back to the current Target and get
its `triple` property.
"""
# TODO
return ""
#MARK:- Command option
def open_wtfautolayout(debugger, command, result, py_context):
"""
When breaking in a stack frame for
UIViewAlertForUnsatisfiableConstraints, opens the constraint error log in
www.wtfautolayout.com
"""
constraint_log = evaluate_arg2(debugger)
if not constraint_log:
result.AppendWarning("Could not evaluate $arg2")
return
# constraint_log now contains the string we need to construct the URL
print >> result, constraint_log # Just for demonstration
# Returning false to continue does not work here because this is a command
def evaluate_arg2(debugger):
"""Get the debugger's interpreter's value for `[$arg2 description]`"""
expr_result = lldb.SBCommandReturnObject()
interpreter = debugger.GetCommandInterpreter()
interpreter.HandleCommand("expression -lobjc -O -- [$arg2 description]",
expr_result)
if expr_result.GetError():
return None
return expr_result.GetOutput()
@ZevEisenberg

This comment has been minimized.

Copy link

ZevEisenberg commented Sep 1, 2018

I tried to accomplish the same thing before coming across your script. Our approaches were somewhat different, but I ran into the same issue with lldb-rpc-server, so I filed a radar: https://openradar.appspot.com/43975320.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.