commands/FBInvocationCommands.py (173 lines of code) (raw):

#!/usr/bin/python # Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved # # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. import re import fbchisellldbbase as fb import lldb def lldbcommands(): return [FBPrintInvocation()] class FBPrintInvocation(fb.FBCommand): def name(self): return "pinvocation" def description(self): return "Print the stack frame, receiver, and arguments of the current invocation. It will fail to print all arguments if any arguments are variadic (varargs).\n\nNOTE: Sadly this is currently only implemented on x86." def options(self): return [ fb.FBCommandArgument( short="-a", long="--all", arg="all", default=False, boolean=True, help="Specify to print the entire stack instead of just the current frame.", ) ] def run(self, arguments, options): target = lldb.debugger.GetSelectedTarget() if not re.match(r".*i386.*", target.GetTriple()): print("Only x86 is currently supported (32-bit iOS Simulator or Mac OS X).") return thread = target.GetProcess().GetSelectedThread() if options.all: for frame in thread: printInvocationForFrame(frame) print("---------------------------------") else: frame = thread.GetSelectedFrame() printInvocationForFrame(frame) def printInvocationForFrame(frame): print(frame) symbolName = frame.GetSymbol().GetName() if not re.match(r"[-+]\s*\[.*\]", symbolName): return self = findArgAtIndexFromStackFrame(frame, 0) cmd = findArgAtIndexFromStackFrame(frame, 1) commandForSignature = ( "[(id)" + self + " methodSignatureForSelector:(char *)sel_getName((SEL)" + cmd + ")]" ) signatureValue = fb.evaluateExpressionValue("(id)" + commandForSignature) if ( signatureValue.GetError() is not None and str(signatureValue.GetError()) != "success" ): print( "My sincerest apologies. I couldn't find a method signature for the selector." ) return signature = signatureValue.GetValue() arg0 = stackStartAddressInSelectedFrame(frame) commandForInvocation = ( "[NSInvocation _invocationWithMethodSignature:(id)" + signature + " frame:((void *)" + str(arg0) + ")]" ) invocation = fb.evaluateExpression("(id)" + commandForInvocation) if invocation: prettyPrintInvocation(frame, invocation) else: print(frame) def stackStartAddressInSelectedFrame(frame): # Determine if the %ebp register has already had the # stack register pushed into it (always the first instruction) frameSymbol = frame.GetSymbolContext(0).GetSymbol() frameStartAddress = frameSymbol.GetStartAddress().GetLoadAddress( lldb.debugger.GetSelectedTarget() ) currentPC = frame.GetPC() offset = currentPC - frameStartAddress if offset == 0: return int(frame.EvaluateExpression("($esp + 4)").GetValue()) elif offset == 1: return int(frame.EvaluateExpression("($esp + 8)").GetValue()) else: return int(frame.EvaluateExpression("($ebp + 8)").GetValue()) def findArgAtIndexFromStackFrame(frame, index): return fb.evaluateExpression( "*(int *)" + str(findArgAdressAtIndexFromStackFrame(frame, index)) ) def findArgAdressAtIndexFromStackFrame(frame, index): arg0 = stackStartAddressInSelectedFrame(frame) arg = arg0 + 4 * index return arg def prettyPrintInvocation(frame, invocation): object = fb.evaluateExpression("(id)[(id)" + invocation + " target]") description = fb.evaluateExpressionValue("(id)" + invocation).GetObjectDescription() argDescriptions = description.splitlines(True)[4:] print("NSInvocation: " + invocation) print("self: " + fb.evaluateExpression("(id)" + object)) if len(argDescriptions) > 0: print( "\n" + str(len(argDescriptions)) + " Arguments:" if len(argDescriptions) > 1 else "\nArgument:" ) index = 2 for argDescription in argDescriptions: s = re.sub(r"argument [0-9]+: ", "", argDescription) address = findArgAdressAtIndexFromStackFrame(frame, index) encoding = s.split(" ")[0] description = " ".join(s.split(" ")[1:]) readableString = argumentAsString(frame, address, encoding) if readableString: print(readableString) else: if encoding[0] == "{": encoding = encoding[1:] print( ( hex(address) + ", address of " + encoding + " " + description ).strip() ) index += 1 def argumentAsString(frame, address, encoding): # noqa C901 if encoding[0] == "{": encoding = encoding[1:] encodingMap = { "c": "char", "i": "int", "s": "short", "l": "long", "q": "long long", "C": "unsigned char", "I": "unsigned int", "S": "unsigned short", "L": "unsigned long", "Q": "unsigned long long", "f": "float", "d": "double", "B": "bool", "v": "void", "*": "char *", "@": "id", "#": "Class", ":": "SEL", } pointers = "" while encoding[0] == "^": pointers += "*" encoding = encoding[1:] type = None if encoding in encodingMap: type = encodingMap[encoding] if type and pointers: type = type + " " + pointers if not type: # Handle simple structs: {CGPoint=ff}, {CGSize=ff}, # {CGRect={CGPoint=ff}{CGSize=ff}} if encoding[0] == "{": encoding = encoding[1:] type = re.sub(r"=.*", "", encoding) if pointers: type += " " + pointers if type: value = frame.EvaluateExpression("*(" + type + " *)" + str(address)) if value.GetError() is None or str(value.GetError()) == "success": description = None if encoding == "@": description = value.GetObjectDescription() if not description: description = value.GetValue() if not description: description = value.GetSummary() if description: return type + ": " + description return None