| # 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 |