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