Last active
September 12, 2018 01:59
-
-
Save woolsweater/8c421b44f6be14d299102d5c84558e15 to your computer and use it in GitHub Desktop.
Break on unsatisfiable constraints and send to wtfautolayout
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
command script import ~/.lldbscripts/break_unsatisfiable.py |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.