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 = ( 112 dumped.GetChildMemberWithName("_cfinfo") 113 .GetChildAtIndex(0 if little_endian else 3) 114 .GetValueAsUnsigned(0) 115 ) 116 is_mutable = (info_bits & 1) == 1 117 is_inline = (info_bits & 0x60) == 0 118 has_explicit_length = (info_bits & (1 | 4)) != 4 119 is_unicode = (info_bits & 0x10) == 0x10 120 is_special = ( 121 nsstring.GetDynamicValue(lldb.eDynamicCanRunTarget).GetTypeName() 122 == "NSPathStore2" 123 ) 124 has_null = (info_bits & 8) == 8 125 126 print( 127 "\nInfo=%d\nMutable=%s\nInline=%s\nExplicit=%s\nUnicode=%s\nSpecial=%s\nNull=%s\n" 128 % ( 129 info_bits, 130 "yes" if is_mutable else "no", 131 "yes" if is_inline else "no", 132 "yes" if has_explicit_length else "no", 133 "yes" if is_unicode else "no", 134 "yes" if is_special else "no", 135 "yes" if has_null else "no", 136 ), 137 file=result, 138 ) 139 140 explicit_length_offset = 0 141 if not has_null and has_explicit_length and not is_special: 142 explicit_length_offset = 2 * ptr_size 143 if is_mutable and not is_inline: 144 explicit_length_offset = explicit_length_offset + ptr_size 145 elif is_inline: 146 pass 147 elif not is_inline and not is_mutable: 148 explicit_length_offset = explicit_length_offset + ptr_size 149 else: 150 explicit_length_offset = 0 151 152 if explicit_length_offset == 0: 153 print("There is no explicit length marker - skipping this step\n", file=result) 154 else: 155 explicit_length_offset = nsstring_address + explicit_length_offset 156 explicit_length = process.ReadUnsignedFromMemory( 157 explicit_length_offset, 4, error 158 ) 159 print( 160 "Explicit length location is at 0x%x - read value is %d\n" 161 % (explicit_length_offset, explicit_length), 162 file=result, 163 ) 164 165 if is_mutable: 166 location = 2 * ptr_size + nsstring_address 167 location = process.ReadPointerFromMemory(location, error) 168 elif ( 169 is_inline 170 and has_explicit_length 171 and not is_unicode 172 and not is_special 173 and not is_mutable 174 ): 175 location = 3 * ptr_size + nsstring_address 176 elif is_unicode: 177 location = 2 * ptr_size + nsstring_address 178 if is_inline: 179 if not has_explicit_length: 180 print( 181 "Unicode & Inline & !Explicit is a new combo - no formula for it", 182 file=result, 183 ) 184 else: 185 location += ptr_size 186 else: 187 location = process.ReadPointerFromMemory(location, error) 188 elif is_special: 189 location = nsstring_address + ptr_size + 4 190 elif is_inline: 191 location = 2 * ptr_size + nsstring_address 192 if not has_explicit_length: 193 location += 1 194 else: 195 location = 2 * ptr_size + nsstring_address 196 location = process.ReadPointerFromMemory(location, error) 197 print("Expected data location: 0x%x\n" % (location), file=result) 198 print( 199 "1K of data around location: %s\n" % read_memory(process, location, 1024), 200 file=result, 201 ) 202 print( 203 "5K of data around string pointer: %s\n" 204 % read_memory(process, nsstring_address, 1024 * 5), 205 file=result, 206 ) 207 208 209def __lldb_init_module(debugger, internal_dict): 210 debugger.HandleCommand( 211 "command script add -o -f %s.diagnose_nsstring_Command_Impl diagnose-nsstring" 212 % __name__ 213 ) 214 print( 215 'The "diagnose-nsstring" command has been installed, type "help diagnose-nsstring" for detailed help.' 216 ) 217 218 219__lldb_init_module(lldb.debugger, None) 220__lldb_init_module = None 221