4 Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
5 See https://llvm.org/LICENSE.txt for license information.
6 SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
8 # example synthetic children and summary provider for CFString (and related NSString class)
9 # the real code is part of the LLDB core
11 import lldb
.runtime
.objc
.objc_runtime
12 import lldb
.formatters
.Logger
20 def CFString_SummaryProvider(valobj
, dict):
21 logger
= lldb
.formatters
.Logger
.Logger()
22 provider
= CFStringSynthProvider(valobj
, dict)
23 if not provider
.invalid
:
25 summary
= provider
.get_child_at_index(provider
.get_child_index("content"))
26 if isinstance(summary
, lldb
.SBValue
):
27 summary
= summary
.GetSummary()
29 summary
= '"' + summary
+ '"'
33 summary
= "<variable is not NSString>"
38 def CFAttributedString_SummaryProvider(valobj
, dict):
39 logger
= lldb
.formatters
.Logger
.Logger()
40 offset
= valobj
.GetTarget().GetProcess().GetAddressByteSize()
41 pointee
= valobj
.GetValueAsUnsigned(0)
42 summary
= "<variable is not NSAttributedString>"
43 if pointee
is not None and pointee
!= 0:
44 pointee
= pointee
+ offset
45 child_ptr
= valobj
.CreateValueFromAddress(
46 "string_ptr", pointee
, valobj
.GetType()
48 child
= child_ptr
.CreateValueFromAddress(
49 "string_data", child_ptr
.GetValueAsUnsigned(), valobj
.GetType()
51 provider
= CFStringSynthProvider(child
, dict)
52 if not provider
.invalid
:
54 summary
= provider
.get_child_at_index(
55 provider
.get_child_index("content")
58 summary
= "<variable is not NSAttributedString>"
60 summary
= "<variable is not NSAttributedString>"
64 def __lldb_init_module(debugger
, dict):
65 debugger
.HandleCommand(
66 "type summary add -F CFString.CFString_SummaryProvider NSString CFStringRef CFMutableStringRef"
68 debugger
.HandleCommand(
69 "type summary add -F CFString.CFAttributedString_SummaryProvider NSAttributedString"
73 class CFStringSynthProvider
:
74 def __init__(self
, valobj
, dict):
75 logger
= lldb
.formatters
.Logger
.Logger()
79 # children other than "content" are for debugging only and must not be
80 # used in production code
81 def num_children(self
):
82 logger
= lldb
.formatters
.Logger
.Logger()
87 def read_unicode(self
, pointer
, max_len
=2048):
88 logger
= lldb
.formatters
.Logger
.Logger()
89 process
= self
.valobj
.GetTarget().GetProcess()
90 error
= lldb
.SBError()
92 # cannot do the read at once because the length value has
93 # a weird encoding. better play it safe here
95 content
= process
.ReadMemory(pointer
, 2, error
)
96 new_bytes
= bytearray(content
)
100 if b0
== 0 and b1
== 0:
102 # rearrange bytes depending on endianness
103 # (do we really need this or is Cocoa going to
104 # use Windows-compatible little-endian even
105 # if the target is big endian?)
107 value
= b1
* 256 + b0
109 value
= b0
* 256 + b1
110 pystr
= pystr
+ unichr(value
)
111 # read max_len unicode values, not max_len bytes
112 max_len
= max_len
- 1
115 # handle the special case strings
116 # only use the custom code for the tested LP64 case
117 def handle_special(self
):
118 logger
= lldb
.formatters
.Logger
.Logger()
119 if not self
.is_64_bit
:
120 # for 32bit targets, use safe ObjC code
121 return self
.handle_unicode_string_safe()
123 pointer
= self
.valobj
.GetValueAsUnsigned(0) + offset
124 pystr
= self
.read_unicode(pointer
)
125 return self
.valobj
.CreateValueFromExpression(
126 "content", '(char*)"' + pystr
.encode("utf-8") + '"'
129 # last resort call, use ObjC code to read; the final aim is to
130 # be able to strip this call away entirely and only do the read
132 def handle_unicode_string_safe(self
):
133 return self
.valobj
.CreateValueFromExpression(
134 "content", '(char*)"' + self
.valobj
.GetObjectDescription() + '"'
137 def handle_unicode_string(self
):
138 logger
= lldb
.formatters
.Logger
.Logger()
139 # step 1: find offset
141 pointer
= self
.valobj
.GetValueAsUnsigned(0) + self
.size_of_cfruntime_base()
142 if not self
.explicit
:
143 # untested, use the safe code path
144 return self
.handle_unicode_string_safe()
146 # a full pointer is skipped here before getting to the live
148 pointer
= pointer
+ self
.pointer_size
150 pointer
= self
.valobj
.GetValueAsUnsigned(0) + self
.size_of_cfruntime_base()
151 # read 8 bytes here and make an address out of them
154 self
.valobj
.GetType()
155 .GetBasicType(lldb
.eBasicTypeChar
)
158 vopointer
= self
.valobj
.CreateValueFromAddress(
159 "dummy", pointer
, char_type
161 pointer
= vopointer
.GetValueAsUnsigned(0)
163 return self
.valobj
.CreateValueFromExpression(
164 "content", '(char*)"@"invalid NSString""'
166 # step 2: read Unicode data at pointer
167 pystr
= self
.read_unicode(pointer
)
169 return pystr
.encode("utf-8")
171 def handle_inline_explicit(self
):
172 logger
= lldb
.formatters
.Logger
.Logger()
173 offset
= 3 * self
.pointer_size
174 offset
= offset
+ self
.valobj
.GetValueAsUnsigned(0)
175 return self
.valobj
.CreateValueFromExpression(
176 "content", "(char*)(" + str(offset
) + ")"
179 def handle_mutable_string(self
):
180 logger
= lldb
.formatters
.Logger
.Logger()
181 offset
= 2 * self
.pointer_size
182 data
= self
.valobj
.CreateChildAtOffset(
185 self
.valobj
.GetType().GetBasicType(lldb
.eBasicTypeChar
).GetPointerType(),
187 data_value
= data
.GetValueAsUnsigned(0)
188 if self
.explicit
and self
.unicode:
189 return self
.read_unicode(data_value
).encode("utf-8")
191 data_value
= data_value
+ 1
192 return self
.valobj
.CreateValueFromExpression(
193 "content", "(char*)(" + str(data_value
) + ")"
196 def handle_UTF8_inline(self
):
197 logger
= lldb
.formatters
.Logger
.Logger()
198 offset
= self
.valobj
.GetValueAsUnsigned(0) + self
.size_of_cfruntime_base()
199 if not self
.explicit
:
201 return self
.valobj
.CreateValueFromAddress(
202 "content", offset
, self
.valobj
.GetType().GetBasicType(lldb
.eBasicTypeChar
)
205 def handle_UTF8_not_inline(self
):
206 logger
= lldb
.formatters
.Logger
.Logger()
207 offset
= self
.size_of_cfruntime_base()
208 return self
.valobj
.CreateChildAtOffset(
211 self
.valobj
.GetType().GetBasicType(lldb
.eBasicTypeChar
).GetPointerType(),
214 def get_child_at_index(self
, index
):
215 logger
= lldb
.formatters
.Logger
.Logger()
216 logger
>> "Querying for child [" + str(index
) + "]"
218 return self
.valobj
.CreateValueFromExpression(
219 "mutable", str(int(self
.mutable
))
222 return self
.valobj
.CreateValueFromExpression(
223 "inline", str(int(self
.inline
))
226 return self
.valobj
.CreateValueFromExpression(
227 "explicit", str(int(self
.explicit
))
230 return self
.valobj
.CreateValueFromExpression(
231 "unicode", str(int(self
.unicode))
234 return self
.valobj
.CreateValueFromExpression(
235 "special", str(int(self
.special
))
238 # we are handling the several possible combinations of flags.
239 # for each known combination we have a function that knows how to
240 # go fetch the data from memory instead of running code. if a string is not
241 # correctly displayed, one should start by finding a combination of flags that
242 # makes it different from these known cases, and provide a new reader function
243 # if this is not possible, a new flag might have to be made up (like the "special" flag
244 # below, which is not a real flag in CFString), or alternatively one might need to use
245 # the ObjC runtime helper to detect the new class and deal with it accordingly
246 # print 'mutable = ' + str(self.mutable)
247 # print 'inline = ' + str(self.inline)
248 # print 'explicit = ' + str(self.explicit)
249 # print 'unicode = ' + str(self.unicode)
250 # print 'special = ' + str(self.special)
252 return self
.handle_mutable_string()
256 and self
.unicode == False
257 and self
.special
== False
258 and self
.mutable
== False
260 return self
.handle_inline_explicit()
262 return self
.handle_unicode_string()
264 return self
.handle_special()
266 return self
.handle_UTF8_inline()
268 return self
.handle_UTF8_not_inline()
270 def get_child_index(self
, name
):
271 logger
= lldb
.formatters
.Logger
.Logger()
272 logger
>> "Querying for child ['" + str(name
) + "']"
273 if name
== "content":
274 return self
.num_children() - 1
275 if name
== "mutable":
279 if name
== "explicit":
281 if name
== "unicode":
283 if name
== "special":
286 # CFRuntimeBase is defined as having an additional
287 # 4 bytes (padding?) on LP64 architectures
288 # to get its size we add up sizeof(pointer)+4
289 # and then add 4 more bytes if we are on a 64bit system
290 def size_of_cfruntime_base(self
):
291 logger
= lldb
.formatters
.Logger
.Logger()
292 return self
.pointer_size
+ 4 + (4 if self
.is_64_bit
else 0)
294 # the info bits are part of the CFRuntimeBase structure
295 # to get at them we have to skip a uintptr_t and then get
296 # at the least-significant byte of a 4 byte array. If we are
297 # on big-endian this means going to byte 3, if we are on
298 # little endian (OSX & iOS), this means reading byte 0
299 def offset_of_info_bits(self
):
300 logger
= lldb
.formatters
.Logger
.Logger()
301 offset
= self
.pointer_size
302 if not self
.is_little
:
306 def read_info_bits(self
):
307 logger
= lldb
.formatters
.Logger
.Logger()
308 cfinfo
= self
.valobj
.CreateChildAtOffset(
310 self
.offset_of_info_bits(),
311 self
.valobj
.GetType().GetBasicType(lldb
.eBasicTypeChar
),
314 info
= cfinfo
.GetValue()
322 # calculating internal flag bits of the CFString object
323 # this stuff is defined and discussed in CFString.c
324 def is_mutable(self
):
325 logger
= lldb
.formatters
.Logger
.Logger()
326 return (self
.info_bits
& 1) == 1
329 logger
= lldb
.formatters
.Logger
.Logger()
330 return (self
.info_bits
& 0x60) == 0
332 # this flag's name is ambiguous, it turns out
333 # we must skip a length byte to get at the data
334 # when this flag is False
335 def has_explicit_length(self
):
336 logger
= lldb
.formatters
.Logger
.Logger()
337 return (self
.info_bits
& (1 |
4)) != 4
339 # probably a subclass of NSString. obtained this from [str pathExtension]
340 # here info_bits = 0 and Unicode data at the start of the padding word
341 # in the long run using the isa value might be safer as a way to identify this
342 # instead of reading the info_bits
343 def is_special_case(self
):
344 logger
= lldb
.formatters
.Logger
.Logger()
345 return self
.info_bits
== 0
347 def is_unicode(self
):
348 logger
= lldb
.formatters
.Logger
.Logger()
349 return (self
.info_bits
& 0x10) == 0x10
351 # preparing ourselves to read into memory
352 # by adjusting architecture-specific info
353 def adjust_for_architecture(self
):
354 logger
= lldb
.formatters
.Logger
.Logger()
355 self
.pointer_size
= self
.valobj
.GetTarget().GetProcess().GetAddressByteSize()
356 self
.is_64_bit
= self
.pointer_size
== 8
358 self
.valobj
.GetTarget().GetProcess().GetByteOrder() == lldb
.eByteOrderLittle
361 # reading info bits out of the CFString and computing
362 # useful values to get at the real data
363 def compute_flags(self
):
364 logger
= lldb
.formatters
.Logger
.Logger()
365 self
.info_bits
= self
.read_info_bits()
366 if self
.info_bits
is None:
368 self
.mutable
= self
.is_mutable()
369 self
.inline
= self
.is_inline()
370 self
.explicit
= self
.has_explicit_length()
371 self
.unicode = self
.is_unicode()
372 self
.special
= self
.is_special_case()
375 logger
= lldb
.formatters
.Logger
.Logger()
376 self
.adjust_for_architecture()