910e62b5创建于 1月15日历史提交
# 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


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):
  # For some reason, the types we get from template variables are not complete.
  # We can replace them by creating a pointer from an expression and use its
  # type.
  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):
    # 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 + 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':
      # 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 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)<.*>$"'
  )