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
10 def read_memory(process
, location
, size
):
12 error
= lldb
.SBError()
13 for x
in range(0, size
- 1):
14 byte
= process
.ReadUnsignedFromMemory(x
+ location
, 1, error
)
16 data
= data
+ "err%s" % "" if x
== size
- 2 else ":"
19 data
= data
+ "0x%x" % byte
31 data
= data
+ "(%s)" % chr(byte
)
34 except Exception as e
:
39 def diagnose_nsstring_Command_Impl(debugger
, command
, result
, internal_dict
):
41 A command to diagnose the LLDB NSString data formatter
43 (lldb) diagnose-nsstring <expr returning NSString>
45 (lldb) diagnose-nsstring @"Hello world"
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()
57 nsstring
= frame
.EvaluateExpression(command
, options
)
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"
65 struct $__lldb__notInlineMutable {\
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\
74 unsigned long desiredCapacity:60;\n\
76 unsigned long desiredCapacity:28;\n\
78 void* contentsAllocator;\
81 struct $__lldb__CFString {\
89 struct __notInlineImmutable1 {\
92 void* contentsDeallocator;\
93 } notInlineImmutable1;\
94 struct __notInlineImmutable2 {\
96 void* contentsDeallocator;\
97 } notInlineImmutable2;\
98 struct $__lldb__notInlineMutable notInlineMutable;\
103 expression
= expression
+ "*(($__lldb__CFString*) %d)" % nsstring_address
105 dumped
= target
.EvaluateExpression(expression
, options
)
106 print(str(dumped
), file=result
)
108 little_endian
= (target
.byte_order
== lldb
.eByteOrderLittle
)
109 ptr_size
= target
.addr_size
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
118 nsstring
.GetDynamicValue(
119 lldb
.eDynamicCanRunTarget
).GetTypeName() == "NSPathStore2")
120 has_null
= (info_bits
& 8) == 8
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
)
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
132 elif not is_inline
and not is_mutable
:
133 explicit_length_offset
= explicit_length_offset
+ ptr_size
135 explicit_length_offset
= 0
137 if explicit_length_offset
== 0:
138 print("There is no explicit length marker - skipping this step\n", file=result
)
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
)
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
152 location
= 2 * ptr_size
+ nsstring_address
154 if not has_explicit_length
:
155 print("Unicode & Inline & !Explicit is a new combo - no formula for it", file=result
)
159 location
= process
.ReadPointerFromMemory(location
, error
)
161 location
= nsstring_address
+ ptr_size
+ 4
163 location
= 2 * ptr_size
+ nsstring_address
164 if not has_explicit_length
:
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
)
176 def __lldb_init_module(debugger
, internal_dict
):
177 debugger
.HandleCommand(
178 "command script add -o -f %s.diagnose_nsstring_Command_Impl diagnose-nsstring" %
180 print('The "diagnose-nsstring" command has been installed, type "help diagnose-nsstring" for detailed help.')
182 __lldb_init_module(lldb
.debugger
, None)
183 __lldb_init_module
= None