221 lines · python
1# This implements the "diagnose-nsstring" command, usually installed in the debug session like2# command script import lldb.diagnose3# it is used when NSString summary formatter fails to replicate the logic that went into LLDB making the4# decisions it did and providing some useful context information that can5# be used for improving the formatter6 7import lldb8 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" % byte20 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 data37 38 39def diagnose_nsstring_Command_Impl(debugger, command, result, internal_dict):40 """41 A command to diagnose the LLDB NSString data formatter42 invoke as43 (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_address104 # print expression105 dumped = target.EvaluateExpression(expression, options)106 print(str(dumped), file=result)107 108 little_endian = target.byte_order == lldb.eByteOrderLittle109 ptr_size = target.addr_size110 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) == 1117 is_inline = (info_bits & 0x60) == 0118 has_explicit_length = (info_bits & (1 | 4)) != 4119 is_unicode = (info_bits & 0x10) == 0x10120 is_special = (121 nsstring.GetDynamicValue(lldb.eDynamicCanRunTarget).GetTypeName()122 == "NSPathStore2"123 )124 has_null = (info_bits & 8) == 8125 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 = 0141 if not has_null and has_explicit_length and not is_special:142 explicit_length_offset = 2 * ptr_size143 if is_mutable and not is_inline:144 explicit_length_offset = explicit_length_offset + ptr_size145 elif is_inline:146 pass147 elif not is_inline and not is_mutable:148 explicit_length_offset = explicit_length_offset + ptr_size149 else:150 explicit_length_offset = 0151 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_offset156 explicit_length = process.ReadUnsignedFromMemory(157 explicit_length_offset, 4, error158 )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_address167 location = process.ReadPointerFromMemory(location, error)168 elif (169 is_inline170 and has_explicit_length171 and not is_unicode172 and not is_special173 and not is_mutable174 ):175 location = 3 * ptr_size + nsstring_address176 elif is_unicode:177 location = 2 * ptr_size + nsstring_address178 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_size186 else:187 location = process.ReadPointerFromMemory(location, error)188 elif is_special:189 location = nsstring_address + ptr_size + 4190 elif is_inline:191 location = 2 * ptr_size + nsstring_address192 if not has_explicit_length:193 location += 1194 else:195 location = 2 * ptr_size + nsstring_address196 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 = None221