|  | # This implements the "diagnose-nsstring" command, usually installed in the debug session like | 
|  | #   command script import lldb.diagnose | 
|  | # it is used when NSString summary formatter fails to replicate the logic that went into LLDB making the | 
|  | # decisions it did and  providing some useful context information that can | 
|  | # be used for improving the formatter | 
|  |  | 
|  | from __future__ import print_function | 
|  |  | 
|  | import lldb | 
|  |  | 
|  |  | 
|  | def read_memory(process, location, size): | 
|  | data = "" | 
|  | error = lldb.SBError() | 
|  | for x in range(0, size - 1): | 
|  | byte = process.ReadUnsignedFromMemory(x + location, 1, error) | 
|  | if error.fail: | 
|  | data = data + "err%s" % "" if x == size - 2 else ":" | 
|  | else: | 
|  | try: | 
|  | data = data + "0x%x" % byte | 
|  | if byte == 0: | 
|  | data = data + "(\\0)" | 
|  | elif byte == 0xa: | 
|  | data = data + "(\\a)" | 
|  | elif byte == 0xb: | 
|  | data = data + "(\\b)" | 
|  | elif byte == 0xc: | 
|  | data = data + "(\\c)" | 
|  | elif byte == '\n': | 
|  | data = data + "(\\n)" | 
|  | else: | 
|  | data = data + "(%s)" % chr(byte) | 
|  | if x < size - 2: | 
|  | data = data + ":" | 
|  | except Exception as e: | 
|  | print(e) | 
|  | return data | 
|  |  | 
|  |  | 
|  | def diagnose_nsstring_Command_Impl(debugger, command, result, internal_dict): | 
|  | """ | 
|  | A command to diagnose the LLDB NSString data formatter | 
|  | invoke as | 
|  | (lldb) diagnose-nsstring <expr returning NSString> | 
|  | e.g. | 
|  | (lldb) diagnose-nsstring @"Hello world" | 
|  | """ | 
|  | target = debugger.GetSelectedTarget() | 
|  | process = target.GetProcess() | 
|  | thread = process.GetSelectedThread() | 
|  | frame = thread.GetSelectedFrame() | 
|  | if not target.IsValid() or not process.IsValid(): | 
|  | return "unable to get target/process - cannot proceed" | 
|  | options = lldb.SBExpressionOptions() | 
|  | options.SetFetchDynamicValue() | 
|  | error = lldb.SBError() | 
|  | if frame.IsValid(): | 
|  | nsstring = frame.EvaluateExpression(command, options) | 
|  | else: | 
|  | nsstring = target.EvaluateExpression(command, options) | 
|  | print(str(nsstring), file=result) | 
|  | nsstring_address = nsstring.GetValueAsUnsigned(0) | 
|  | if nsstring_address == 0: | 
|  | return "unable to obtain the string - cannot proceed" | 
|  | expression = "\ | 
|  | struct $__lldb__notInlineMutable {\ | 
|  | char* buffer;\ | 
|  | signed long length;\ | 
|  | signed long capacity;\ | 
|  | unsigned int hasGap:1;\ | 
|  | unsigned int isFixedCapacity:1;\ | 
|  | unsigned int isExternalMutable:1;\ | 
|  | unsigned int capacityProvidedExternally:1;\n\ | 
|  | #if __LP64__\n\ | 
|  | unsigned long desiredCapacity:60;\n\ | 
|  | #else\n\ | 
|  | unsigned long desiredCapacity:28;\n\ | 
|  | #endif\n\ | 
|  | void* contentsAllocator;\ | 
|  | };\ | 
|  | \ | 
|  | struct $__lldb__CFString {\ | 
|  | void* _cfisa;\ | 
|  | uint8_t _cfinfo[4];\ | 
|  | uint32_t _rc;\ | 
|  | union {\ | 
|  | struct __inline1 {\ | 
|  | signed long length;\ | 
|  | } inline1;\ | 
|  | struct __notInlineImmutable1 {\ | 
|  | char* buffer;\ | 
|  | signed long length;\ | 
|  | void* contentsDeallocator;\ | 
|  | } notInlineImmutable1;\ | 
|  | struct __notInlineImmutable2 {\ | 
|  | char* buffer;\ | 
|  | void* contentsDeallocator;\ | 
|  | } notInlineImmutable2;\ | 
|  | struct $__lldb__notInlineMutable notInlineMutable;\ | 
|  | } variants;\ | 
|  | };\ | 
|  | " | 
|  |  | 
|  | expression = expression + "*(($__lldb__CFString*) %d)" % nsstring_address | 
|  | # print expression | 
|  | dumped = target.EvaluateExpression(expression, options) | 
|  | print(str(dumped), file=result) | 
|  |  | 
|  | little_endian = (target.byte_order == lldb.eByteOrderLittle) | 
|  | ptr_size = target.addr_size | 
|  |  | 
|  | info_bits = dumped.GetChildMemberWithName("_cfinfo").GetChildAtIndex( | 
|  | 0 if little_endian else 3).GetValueAsUnsigned(0) | 
|  | is_mutable = (info_bits & 1) == 1 | 
|  | is_inline = (info_bits & 0x60) == 0 | 
|  | has_explicit_length = (info_bits & (1 | 4)) != 4 | 
|  | is_unicode = (info_bits & 0x10) == 0x10 | 
|  | is_special = ( | 
|  | nsstring.GetDynamicValue( | 
|  | lldb.eDynamicCanRunTarget).GetTypeName() == "NSPathStore2") | 
|  | has_null = (info_bits & 8) == 8 | 
|  |  | 
|  | print("\nInfo=%d\nMutable=%s\nInline=%s\nExplicit=%s\nUnicode=%s\nSpecial=%s\nNull=%s\n" % \ | 
|  | (info_bits, "yes" if is_mutable else "no", "yes" if is_inline else "no", "yes" if has_explicit_length else "no", "yes" if is_unicode else "no", "yes" if is_special else "no", "yes" if has_null else "no"), file=result) | 
|  |  | 
|  | explicit_length_offset = 0 | 
|  | if not has_null and has_explicit_length and not is_special: | 
|  | explicit_length_offset = 2 * ptr_size | 
|  | if is_mutable and not is_inline: | 
|  | explicit_length_offset = explicit_length_offset + ptr_size | 
|  | elif is_inline: | 
|  | pass | 
|  | elif not is_inline and not is_mutable: | 
|  | explicit_length_offset = explicit_length_offset + ptr_size | 
|  | else: | 
|  | explicit_length_offset = 0 | 
|  |  | 
|  | if explicit_length_offset == 0: | 
|  | print("There is no explicit length marker - skipping this step\n", file=result) | 
|  | else: | 
|  | explicit_length_offset = nsstring_address + explicit_length_offset | 
|  | explicit_length = process.ReadUnsignedFromMemory( | 
|  | explicit_length_offset, 4, error) | 
|  | print("Explicit length location is at 0x%x - read value is %d\n" % ( | 
|  | explicit_length_offset, explicit_length), file=result) | 
|  |  | 
|  | if is_mutable: | 
|  | location = 2 * ptr_size + nsstring_address | 
|  | location = process.ReadPointerFromMemory(location, error) | 
|  | elif is_inline and has_explicit_length and not is_unicode and not is_special and not is_mutable: | 
|  | location = 3 * ptr_size + nsstring_address | 
|  | elif is_unicode: | 
|  | location = 2 * ptr_size + nsstring_address | 
|  | if is_inline: | 
|  | if not has_explicit_length: | 
|  | print("Unicode & Inline & !Explicit is a new combo - no formula for it", file=result) | 
|  | else: | 
|  | location += ptr_size | 
|  | else: | 
|  | location = process.ReadPointerFromMemory(location, error) | 
|  | elif is_special: | 
|  | location = nsstring_address + ptr_size + 4 | 
|  | elif is_inline: | 
|  | location = 2 * ptr_size + nsstring_address | 
|  | if not has_explicit_length: | 
|  | location += 1 | 
|  | else: | 
|  | location = 2 * ptr_size + nsstring_address | 
|  | location = process.ReadPointerFromMemory(location, error) | 
|  | print("Expected data location: 0x%x\n" % (location), file=result) | 
|  | print("1K of data around location: %s\n" % read_memory( | 
|  | process, location, 1024), file=result) | 
|  | print("5K of data around string pointer: %s\n" % read_memory( | 
|  | process, nsstring_address, 1024 * 5), file=result) | 
|  |  | 
|  |  | 
|  | def __lldb_init_module(debugger, internal_dict): | 
|  | debugger.HandleCommand( | 
|  | "command script add -f %s.diagnose_nsstring_Command_Impl diagnose-nsstring" % | 
|  | __name__) | 
|  | print('The "diagnose-nsstring" command has been installed, type "help diagnose-nsstring" for detailed help.') | 
|  |  | 
|  | __lldb_init_module(lldb.debugger, None) | 
|  | __lldb_init_module = None |