| # Copyright 2025 The Chromium Authors |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| """ |
| 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 |
| |
| |
| kPointerCompressionShift_getter = lazy_unsigned_global( |
| 'cppgc::internal::api_constants::kPointerCompressionShift') |
| CageBaseGlobal_getter = lazy_unsigned_global( |
| 'cppgc::internal::CageBaseGlobal::g_base_', 'base') |
| |
| PLACEHOLDER_VALUE = None |
| |
| |
| class SingleChildProvider: |
| """A base class for providers that create one child.""" |
| |
| def __init__(self, valobj): |
| self.child = None |
| self.valobj = valobj |
| |
| def update(self): |
| # By default, reevaluate |
| 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 |
| |
| def has_children(self): |
| return True |
| |
| def get_child_index(self, name): |
| self.ensure_populated() |
| return 0 |
| |
| def get_child_at_index(self, index): |
| self.ensure_populated() |
| return self.child |
| |
| |
| 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 calling a |
| method on the object. |
| """ |
| |
| def __init__(self, |
| raw_name, |
| valobj, |
| internal_dict, |
| child_name='$$dereference$$'): |
| 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): |
| self.child = self.valobj.GetChildMemberWithName(self.raw_name).Cast( |
| self.valobj.GetType().GetTemplateArgumentType(0).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': |
| # Need to reproduce the behavior of CompressedPointer::Decompress() |
| # because it is optimized away. |
| 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: |
| # Last child is still valid |
| 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: |
| # This seems to happen when the pointer is null. Ignore. |
| return |
| self.child = data_pointer |
| self.last_child = self.child |
| |
| |
| class MethodProvider(SingleChildProvider): |
| """ |
| A base class for providers that create one child generated by calling a |
| method on the object. |
| """ |
| |
| def __init__(self, |
| method, |
| valobj, |
| internal_dict, |
| child_name='$$dereference$$'): |
| super().__init__(valobj) |
| self.method = method |
| self.child_name = child_name |
| self.last_child = None |
| self.last_addr = None |
| |
| def populate(self): |
| lldbtype = self.valobj.GetType() |
| addr = self.valobj.GetValueAsAddress() if lldbtype.IsPointerType( |
| ) else self.valobj.GetLoadAddress() |
| if self.last_child and self.last_addr == addr: |
| # Last child is still valid |
| self.child = self.last_child |
| return |
| self.child = self.valobj.CreateValueFromExpression( |
| self.child_name, |
| f'(({lldbtype.GetCanonicalType().GetName()} *){addr})->{self.method}') |
| self.last_child = self.child |
| self.last_addr = addr |
| |
| |
| class WTFStringProvider(MethodProvider): |
| |
| def __init__(self, valobj, internal_dict): |
| super().__init__('Utf8(WTF::Utf8ConversionMode::kLenient)', valobj, |
| internal_dict, 'utf8_') |
| |
| |
| 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<.*>$"' |
| ) |
| debugger.HandleCommand( |
| 'type synthetic add -l chromium_visualizers.WTFStringProvider -x "^WTF::String$"' |
| ) |
| debugger.HandleCommand( |
| 'type synthetic add -l chromium_visualizers.VectorProvider -x "^WTF::Vector<.*>$"' |
| ) |
| debugger.HandleCommand( |
| 'type summary add --summary-string "${svar.utf8_}" WTF::String') |
| debugger.HandleCommand( |
| 'type summary add --summary-string "${var.string_}" WTF::AtomicString') |
| debugger.HandleCommand( |
| 'type summary add --summary-string "size = ${var.size_}" -x "^WTF::Vector<.*>$"' |
| ) |
| debugger.HandleCommand( |
| 'type summary add --summary-string "${var.raw_}" -x "^cppgc::internal::BasicMember<.*>$"' |
| ) |