"""
LLDB AppKit formatters
Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
See https://llvm.org/LICENSE.txt for license information.
SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
"""
import lldb
import lldb.runtime.objc.objc_runtime
import lldb.formatters.Logger
try:
unichr
except NameError:
unichr = chr
def CFString_SummaryProvider(valobj, dict):
logger = lldb.formatters.Logger.Logger()
provider = CFStringSynthProvider(valobj, dict)
if not provider.invalid:
try:
summary = provider.get_child_at_index(provider.get_child_index("content"))
if isinstance(summary, lldb.SBValue):
summary = summary.GetSummary()
else:
summary = '"' + summary + '"'
except:
summary = None
if summary is None:
summary = "<variable is not NSString>"
return "@" + summary
return ""
def CFAttributedString_SummaryProvider(valobj, dict):
logger = lldb.formatters.Logger.Logger()
offset = valobj.GetTarget().GetProcess().GetAddressByteSize()
pointee = valobj.GetValueAsUnsigned(0)
summary = "<variable is not NSAttributedString>"
if pointee is not None and pointee != 0:
pointee = pointee + offset
child_ptr = valobj.CreateValueFromAddress(
"string_ptr", pointee, valobj.GetType()
)
child = child_ptr.CreateValueFromAddress(
"string_data", child_ptr.GetValueAsUnsigned(), valobj.GetType()
).AddressOf()
provider = CFStringSynthProvider(child, dict)
if not provider.invalid:
try:
summary = provider.get_child_at_index(
provider.get_child_index("content")
).GetSummary()
except:
summary = "<variable is not NSAttributedString>"
if summary is None:
summary = "<variable is not NSAttributedString>"
return "@" + summary
def __lldb_init_module(debugger, dict):
debugger.HandleCommand(
"type summary add -F CFString.CFString_SummaryProvider NSString CFStringRef CFMutableStringRef"
)
debugger.HandleCommand(
"type summary add -F CFString.CFAttributedString_SummaryProvider NSAttributedString"
)
class CFStringSynthProvider:
def __init__(self, valobj, dict):
logger = lldb.formatters.Logger.Logger()
self.valobj = valobj
self.update()
def num_children(self):
logger = lldb.formatters.Logger.Logger()
if self.invalid:
return 0
return 6
def read_unicode(self, pointer, max_len=2048):
logger = lldb.formatters.Logger.Logger()
process = self.valobj.GetTarget().GetProcess()
error = lldb.SBError()
pystr = ""
while max_len > 0:
content = process.ReadMemory(pointer, 2, error)
new_bytes = bytearray(content)
b0 = new_bytes[0]
b1 = new_bytes[1]
pointer = pointer + 2
if b0 == 0 and b1 == 0:
break
if self.is_little:
value = b1 * 256 + b0
else:
value = b0 * 256 + b1
pystr = pystr + unichr(value)
max_len = max_len - 1
return pystr
def handle_special(self):
logger = lldb.formatters.Logger.Logger()
if not self.is_64_bit:
return self.handle_unicode_string_safe()
offset = 12
pointer = self.valobj.GetValueAsUnsigned(0) + offset
pystr = self.read_unicode(pointer)
return self.valobj.CreateValueFromExpression(
"content", '(char*)"' + pystr.encode("utf-8") + '"'
)
def handle_unicode_string_safe(self):
return self.valobj.CreateValueFromExpression(
"content", '(char*)"' + self.valobj.GetObjectDescription() + '"'
)
def handle_unicode_string(self):
logger = lldb.formatters.Logger.Logger()
if self.inline:
pointer = self.valobj.GetValueAsUnsigned(0) + self.size_of_cfruntime_base()
if not self.explicit:
return self.handle_unicode_string_safe()
else:
pointer = pointer + self.pointer_size
else:
pointer = self.valobj.GetValueAsUnsigned(0) + self.size_of_cfruntime_base()
try:
char_type = (
self.valobj.GetType()
.GetBasicType(lldb.eBasicTypeChar)
.GetPointerType()
)
vopointer = self.valobj.CreateValueFromAddress(
"dummy", pointer, char_type
)
pointer = vopointer.GetValueAsUnsigned(0)
except:
return self.valobj.CreateValueFromExpression(
"content", '(char*)"@"invalid NSString""'
)
pystr = self.read_unicode(pointer)
return pystr.encode("utf-8")
def handle_inline_explicit(self):
logger = lldb.formatters.Logger.Logger()
offset = 3 * self.pointer_size
offset = offset + self.valobj.GetValueAsUnsigned(0)
return self.valobj.CreateValueFromExpression(
"content", "(char*)(" + str(offset) + ")"
)
def handle_mutable_string(self):
logger = lldb.formatters.Logger.Logger()
offset = 2 * self.pointer_size
data = self.valobj.CreateChildAtOffset(
"content",
offset,
self.valobj.GetType().GetBasicType(lldb.eBasicTypeChar).GetPointerType(),
)
data_value = data.GetValueAsUnsigned(0)
if self.explicit and self.unicode:
return self.read_unicode(data_value).encode("utf-8")
else:
data_value = data_value + 1
return self.valobj.CreateValueFromExpression(
"content", "(char*)(" + str(data_value) + ")"
)
def handle_UTF8_inline(self):
logger = lldb.formatters.Logger.Logger()
offset = self.valobj.GetValueAsUnsigned(0) + self.size_of_cfruntime_base()
if not self.explicit:
offset = offset + 1
return self.valobj.CreateValueFromAddress(
"content", offset, self.valobj.GetType().GetBasicType(lldb.eBasicTypeChar)
).AddressOf()
def handle_UTF8_not_inline(self):
logger = lldb.formatters.Logger.Logger()
offset = self.size_of_cfruntime_base()
return self.valobj.CreateChildAtOffset(
"content",
offset,
self.valobj.GetType().GetBasicType(lldb.eBasicTypeChar).GetPointerType(),
)
def get_child_at_index(self, index):
logger = lldb.formatters.Logger.Logger()
logger >> "Querying for child [" + str(index) + "]"
if index == 0:
return self.valobj.CreateValueFromExpression(
"mutable", str(int(self.mutable))
)
if index == 1:
return self.valobj.CreateValueFromExpression(
"inline", str(int(self.inline))
)
if index == 2:
return self.valobj.CreateValueFromExpression(
"explicit", str(int(self.explicit))
)
if index == 3:
return self.valobj.CreateValueFromExpression(
"unicode", str(int(self.unicode))
)
if index == 4:
return self.valobj.CreateValueFromExpression(
"special", str(int(self.special))
)
if index == 5:
if self.mutable:
return self.handle_mutable_string()
elif (
self.inline
and self.explicit
and not self.unicode
and not self.special
and not self.mutable
):
return self.handle_inline_explicit()
elif self.unicode:
return self.handle_unicode_string()
elif self.special:
return self.handle_special()
elif self.inline:
return self.handle_UTF8_inline()
else:
return self.handle_UTF8_not_inline()
def get_child_index(self, name):
logger = lldb.formatters.Logger.Logger()
logger >> "Querying for child ['" + str(name) + "']"
if name == "content":
return self.num_children() - 1
if name == "mutable":
return 0
if name == "inline":
return 1
if name == "explicit":
return 2
if name == "unicode":
return 3
if name == "special":
return 4
def size_of_cfruntime_base(self):
logger = lldb.formatters.Logger.Logger()
return self.pointer_size + 4 + (4 if self.is_64_bit else 0)
def offset_of_info_bits(self):
logger = lldb.formatters.Logger.Logger()
offset = self.pointer_size
if not self.is_little:
offset = offset + 3
return offset
def read_info_bits(self):
logger = lldb.formatters.Logger.Logger()
cfinfo = self.valobj.CreateChildAtOffset(
"cfinfo",
self.offset_of_info_bits(),
self.valobj.GetType().GetBasicType(lldb.eBasicTypeChar),
)
cfinfo.SetFormat(11)
info = cfinfo.GetValue()
if info is not None:
self.invalid = False
return int(info, 0)
else:
self.invalid = True
return None
def is_mutable(self):
logger = lldb.formatters.Logger.Logger()
return (self.info_bits & 1) == 1
def is_inline(self):
logger = lldb.formatters.Logger.Logger()
return (self.info_bits & 0x60) == 0
def has_explicit_length(self):
logger = lldb.formatters.Logger.Logger()
return (self.info_bits & (1 | 4)) != 4
def is_special_case(self):
logger = lldb.formatters.Logger.Logger()
return self.info_bits == 0
def is_unicode(self):
logger = lldb.formatters.Logger.Logger()
return (self.info_bits & 0x10) == 0x10
def adjust_for_architecture(self):
logger = lldb.formatters.Logger.Logger()
self.pointer_size = self.valobj.GetTarget().GetProcess().GetAddressByteSize()
self.is_64_bit = self.pointer_size == 8
self.is_little = (
self.valobj.GetTarget().GetProcess().GetByteOrder() == lldb.eByteOrderLittle
)
def compute_flags(self):
logger = lldb.formatters.Logger.Logger()
self.info_bits = self.read_info_bits()
if self.info_bits is None:
return
self.mutable = self.is_mutable()
self.inline = self.is_inline()
self.explicit = self.has_explicit_length()
self.unicode = self.is_unicode()
self.special = self.is_special_case()
def update(self):
logger = lldb.formatters.Logger.Logger()
self.adjust_for_architecture()
self.compute_flags()