1# This implements the "diagnose-nsstring" command, usually installed in the debug session like 2# command script import lldb.diagnose 3# it is used when NSString summary formatter fails to replicate the logic that went into LLDB making the 4# decisions it did and providing some useful context information that can 5# be used for improving the formatter 6 7import lldb 8 9 10def read_memory(process, location, size): 11 data = "" 12 error = lldb.SBError() 13 for x in range(0, size - 1): 14 byte = process.ReadUnsignedFromMemory(x + location, 1, error) 15 if error.fail: 16 data = data + "err%s" % "" if x == size - 2 else ":" 17 else: 18 try: 19 data = data + "0x%x" % byte 20 if byte == 0: 21 data = data + "(\\0)" 22 elif byte == 0xa: 23 data = data + "(\\a)" 24 elif byte == 0xb: 25 data = data + "(\\b)" 26 elif byte == 0xc: 27 data = data + "(\\c)" 28 elif byte == '\n': 29 data = data + "(\\n)" 30 else: 31 data = data + "(%s)" % chr(byte) 32 if x < size - 2: 33 data = data + ":" 34 except Exception as e: 35 print(e) 36 return data 37 38 39def diagnose_nsstring_Command_Impl(debugger, command, result, internal_dict): 40 """ 41 A command to diagnose the LLDB NSString data formatter 42 invoke as 43 (lldb) diagnose-nsstring <expr returning NSString> 44 e.g. 45 (lldb) diagnose-nsstring @"Hello world" 46 """ 47 target = debugger.GetSelectedTarget() 48 process = target.GetProcess() 49 thread = process.GetSelectedThread() 50 frame = thread.GetSelectedFrame() 51 if not target.IsValid() or not process.IsValid(): 52 return "unable to get target/process - cannot proceed" 53 options = lldb.SBExpressionOptions() 54 options.SetFetchDynamicValue() 55 error = lldb.SBError() 56 if frame.IsValid(): 57 nsstring = frame.EvaluateExpression(command, options) 58 else: 59 nsstring = target.EvaluateExpression(command, options) 60 print(str(nsstring), file=result) 61 nsstring_address = nsstring.GetValueAsUnsigned(0) 62 if nsstring_address == 0: 63 return "unable to obtain the string - cannot proceed" 64 expression = "\ 65struct $__lldb__notInlineMutable {\ 66 char* buffer;\ 67 signed long length;\ 68 signed long capacity;\ 69 unsigned int hasGap:1;\ 70 unsigned int isFixedCapacity:1;\ 71 unsigned int isExternalMutable:1;\ 72 unsigned int capacityProvidedExternally:1;\n\ 73#if __LP64__\n\ 74 unsigned long desiredCapacity:60;\n\ 75#else\n\ 76 unsigned long desiredCapacity:28;\n\ 77#endif\n\ 78 void* contentsAllocator;\ 79};\ 80\ 81struct $__lldb__CFString {\ 82 void* _cfisa;\ 83 uint8_t _cfinfo[4];\ 84 uint32_t _rc;\ 85 union {\ 86 struct __inline1 {\ 87 signed long length;\ 88 } inline1;\ 89 struct __notInlineImmutable1 {\ 90 char* buffer;\ 91 signed long length;\ 92 void* contentsDeallocator;\ 93 } notInlineImmutable1;\ 94 struct __notInlineImmutable2 {\ 95 char* buffer;\ 96 void* contentsDeallocator;\ 97 } notInlineImmutable2;\ 98 struct $__lldb__notInlineMutable notInlineMutable;\ 99 } variants;\ 100};\ 101" 102 103 expression = expression + "*(($__lldb__CFString*) %d)" % nsstring_address 104 # print expression 105 dumped = target.EvaluateExpression(expression, options) 106 print(str(dumped), file=result) 107 108 little_endian = (target.byte_order == lldb.eByteOrderLittle) 109 ptr_size = target.addr_size 110 111 info_bits = dumped.GetChildMemberWithName("_cfinfo").GetChildAtIndex( 112 0 if little_endian else 3).GetValueAsUnsigned(0) 113 is_mutable = (info_bits & 1) == 1 114 is_inline = (info_bits & 0x60) == 0 115 has_explicit_length = (info_bits & (1 | 4)) != 4 116 is_unicode = (info_bits & 0x10) == 0x10 117 is_special = ( 118 nsstring.GetDynamicValue( 119 lldb.eDynamicCanRunTarget).GetTypeName() == "NSPathStore2") 120 has_null = (info_bits & 8) == 8 121 122 print("\nInfo=%d\nMutable=%s\nInline=%s\nExplicit=%s\nUnicode=%s\nSpecial=%s\nNull=%s\n" % \ 123 (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) 124 125 explicit_length_offset = 0 126 if not has_null and has_explicit_length and not is_special: 127 explicit_length_offset = 2 * ptr_size 128 if is_mutable and not is_inline: 129 explicit_length_offset = explicit_length_offset + ptr_size 130 elif is_inline: 131 pass 132 elif not is_inline and not is_mutable: 133 explicit_length_offset = explicit_length_offset + ptr_size 134 else: 135 explicit_length_offset = 0 136 137 if explicit_length_offset == 0: 138 print("There is no explicit length marker - skipping this step\n", file=result) 139 else: 140 explicit_length_offset = nsstring_address + explicit_length_offset 141 explicit_length = process.ReadUnsignedFromMemory( 142 explicit_length_offset, 4, error) 143 print("Explicit length location is at 0x%x - read value is %d\n" % ( 144 explicit_length_offset, explicit_length), file=result) 145 146 if is_mutable: 147 location = 2 * ptr_size + nsstring_address 148 location = process.ReadPointerFromMemory(location, error) 149 elif is_inline and has_explicit_length and not is_unicode and not is_special and not is_mutable: 150 location = 3 * ptr_size + nsstring_address 151 elif is_unicode: 152 location = 2 * ptr_size + nsstring_address 153 if is_inline: 154 if not has_explicit_length: 155 print("Unicode & Inline & !Explicit is a new combo - no formula for it", file=result) 156 else: 157 location += ptr_size 158 else: 159 location = process.ReadPointerFromMemory(location, error) 160 elif is_special: 161 location = nsstring_address + ptr_size + 4 162 elif is_inline: 163 location = 2 * ptr_size + nsstring_address 164 if not has_explicit_length: 165 location += 1 166 else: 167 location = 2 * ptr_size + nsstring_address 168 location = process.ReadPointerFromMemory(location, error) 169 print("Expected data location: 0x%x\n" % (location), file=result) 170 print("1K of data around location: %s\n" % read_memory( 171 process, location, 1024), file=result) 172 print("5K of data around string pointer: %s\n" % read_memory( 173 process, nsstring_address, 1024 * 5), file=result) 174 175 176def __lldb_init_module(debugger, internal_dict): 177 debugger.HandleCommand( 178 "command script add -o -f %s.diagnose_nsstring_Command_Impl diagnose-nsstring" % 179 __name__) 180 print('The "diagnose-nsstring" command has been installed, type "help diagnose-nsstring" for detailed help.') 181 182__lldb_init_module(lldb.debugger, None) 183__lldb_init_module = None 184