"""
Provides visualizers for common types for debugging in LLDB.
To make these available, add the following to your ~/.lldbinit or your
.vscode/launch.json, or run after launching lldb:
command script import {Path to SRC Root}/tools/lldb/chromium_visualizers.py
"""
import traceback
import lldb
def lazy_unsigned_global(name, child=None):
sb_value = None
uint_value = 0
def getter(fromValue):
nonlocal sb_value
nonlocal uint_value
if sb_value is None:
sb_value = fromValue.GetTarget().FindFirstGlobalVariable(name)
if child:
sb_value = sb_value.GetChildMemberWithName(child)
uint_value = sb_value.GetValueAsUnsigned()
return uint_value
return getter
def lazy_expression(expr):
sb_value = None
uint_value = 0
def getter(fromValue):
nonlocal sb_value
nonlocal uint_value
if sb_value is None:
sb_value = fromValue.CreateValueFromExpression('expr', expr)
uint_value = sb_value.GetValueAsUnsigned()
return uint_value
return getter
complete_types = dict()
def get_complete_type(lldb_type, valobj):
if lldb_type.IsTypeComplete():
return lldb_type
name = lldb_type.GetName()
if name in complete_types:
return complete_types[name]
null_valobj = valobj.CreateValueFromExpression('null', f'({name}*)0')
if null_valobj.IsValid():
complete_type = null_valobj.GetType().GetPointeeType()
complete_types[name] = complete_type
return complete_type
return lldb_type
kPointerCompressionShift_getter = lazy_unsigned_global(
'cppgc::internal::api_constants::kPointerCompressionShift')
CageBaseGlobal_getter = lazy_unsigned_global(
'cppgc::internal::CageBaseGlobal::g_base_', 'base')
kIs8Bit_getter = lazy_expression('(int)(blink::StringImpl::Flags::kIs8Bit)')
PLACEHOLDER_VALUE = None
DEREFERENCE_NAME = '$$dereference$$'
class SingleChildProvider:
"""A base class for providers that create one child."""
def __init__(self, valobj):
self.child = None
self.valobj = valobj
def update(self):
self.child = None
def ensure_populated(self):
if self.child is None:
try:
self.populate()
except:
print(traceback.format_exc())
if self.child is None or not self.child.IsValid():
global PLACEHOLDER_VALUE
if PLACEHOLDER_VALUE is None:
PLACEHOLDER_VALUE = self.valobj.CreateValueFromExpression(
'<failed to load child>', 'nullptr')
self.child = PLACEHOLDER_VALUE
def populate(self):
raise NotImplementedError()
def num_children(self):
self.ensure_populated()
return 1 + self.valobj.GetNumChildren()
def has_children(self):
return True
def get_child_index(self, name):
self.ensure_populated()
if name == self.child.GetName():
return 0
return 1 + self.valobj.GetIndexOfChildWithName(name)
def get_child_at_index(self, index):
self.ensure_populated()
if index == 0:
return self.child
return self.valobj.GetChildAtIndex(index - 1)
class VectorProvider(SingleChildProvider):
"""Provides children for a vector."""
def __init__(self, valobj, internal_dict):
super().__init__(valobj)
def populate(self):
lldbtype = self.valobj.GetType()
addr = self.valobj.GetValueAsAddress() if lldbtype.IsPointerType(
) else self.valobj.GetLoadAddress()
data_pointer = self.valobj.GetChildMemberWithName('buffer_')
size = self.valobj.GetChildMemberWithName('size_').GetValueAsUnsigned()
self.child = data_pointer.Cast(
data_pointer.GetType().GetPointeeType().GetArrayType(
size).GetPointerType())
class SmartPointerProvider(SingleChildProvider):
"""
A base class for providers that create one child generated by getting a
member and casting it to a pointer type on the object.
"""
def __init__(self,
raw_name,
valobj,
internal_dict,
child_name=DEREFERENCE_NAME):
super().__init__(valobj)
self.raw_name = raw_name
self.child_name = child_name
def update(self):
global PLACEHOLDER_VALUE
if self.child is PLACEHOLDER_VALUE:
self.child = None
def populate(self):
pointee_type = get_complete_type(
self.valobj.GetType().GetTemplateArgumentType(0), self.valobj)
self.child = self.valobj.GetChildMemberWithName(self.raw_name).Cast(
pointee_type.GetPointerType())
class ScopedRefProvider(SmartPointerProvider):
def __init__(self, valobj, internal_dict):
super().__init__('ptr_', valobj, internal_dict)
class MemberProvider(SingleChildProvider):
def __init__(self, valobj, internal_dict):
super().__init__(valobj)
self.last_child = None
self.last_addr = None
self.raw_storage = None
self.pointer_type = None
self.compressed = None
def update(self):
global PLACEHOLDER_VALUE
if self.compressed is not None or self.child is PLACEHOLDER_VALUE:
self.child = None
def populate(self):
if self.raw_storage is None:
self.raw_storage = self.valobj.GetChildMemberWithName('raw_')
self.pointer_type = self.raw_storage.GetType().GetCanonicalType().GetName(
)
pointee_type = self.valobj.GetType().GetTemplateArgumentType(0)
if self.pointer_type == 'cppgc::internal::RawPointer':
data_pointer = self.raw_storage.GetChildMemberWithName('ptr_').Cast(
pointee_type.GetPointerType())
elif self.pointer_type == 'cppgc::internal::CompressedPointer':
if self.compressed is None:
self.compressed = self.raw_storage.GetChildMemberWithName('value_')
compressed = self.compressed.GetValueAsUnsigned()
if self.last_child and self.last_addr == compressed:
self.child = self.last_child
return
pointer_shift = kPointerCompressionShift_getter(self.raw_storage)
cage_base = CageBaseGlobal_getter(self.raw_storage)
sign_bit = 0x80000000
decompressed = ((
(compressed ^ sign_bit) - sign_bit) << pointer_shift) & cage_base
data_pointer = self.valobj.CreateValueFromAddress('$$dereference$$',
decompressed,
pointee_type)
self.last_addr = compressed
else:
return
self.child = data_pointer
self.last_child = self.child
class WTFStringProvider(SingleChildProvider):
def __init__(self, valobj, internal_dict):
super().__init__(valobj)
def populate(self):
smart_pointer = self.valobj.GetChildMemberWithName('impl_')
raw_pointer = smart_pointer.GetChildMemberWithName('ptr_')
addr = raw_pointer.GetValueAsUnsigned()
if addr == 0:
self.child = raw_pointer
return
impl_type = get_complete_type(
smart_pointer.GetType().GetTemplateArgumentType(0), self.valobj)
impl = self.valobj.CreateValueFromAddress('impl', addr, impl_type)
hash_and_flags = impl.GetChildMemberWithName(
'hash_and_flags_').GetChildMemberWithName('Value').GetValueAsUnsigned()
is8bit = hash_and_flags & kIs8Bit_getter(self.valobj)
char_type = impl_type.GetBasicType(
lldb.eBasicTypeChar if is8bit else lldb.eBasicTypeChar16)
strlen = impl.GetChildMemberWithName('length_').GetValueAsUnsigned()
addr += impl_type.GetByteSize()
self.child = self.valobj.CreateValueFromAddress(
'string_', addr, char_type.GetArrayType(strlen))
def __lldb_init_module(debugger, unused_dict):
debugger.HandleCommand(
'type synthetic add -l chromium_visualizers.ScopedRefProvider -x "^scoped_refptr<.*>$"'
)
debugger.HandleCommand(
'type synthetic add -p -r -l chromium_visualizers.MemberProvider -x "^(cppgc::internal::BasicMember|blink::Member)<.*>$"'
)
debugger.HandleCommand(
'type synthetic add -l chromium_visualizers.WTFStringProvider -x "^blink::String$"'
)
debugger.HandleCommand(
'type synthetic add -l chromium_visualizers.VectorProvider -x "^blink::Vector<.*>$"'
)
debugger.HandleCommand(
'type summary add --summary-string "${svar.string_}" blink::String')
debugger.HandleCommand(
'type summary add --summary-string "${var.string_}" blink::AtomicString')
debugger.HandleCommand(
'type summary add --summary-string "size = ${var.size_}" -x "^blink::Vector<.*>$"'
)
debugger.HandleCommand(
'type summary add --summary-string "${var.raw_}" -x "^(cppgc::internal::BasicMember|blink::Member)<.*>$"'
)