| from __future__ import annotations | |
| import sys | |
| from enum import Flag, auto | |
| from typing import TYPE_CHECKING, Dict, Generator, List, Optional | |
| from lldb import ( | |
| SBData, | |
| SBError, | |
| eBasicTypeChar32, | |
| eBasicTypeDouble, | |
| eBasicTypeFloat, | |
| eBasicTypeHalf, | |
| eBasicTypeLong, | |
| eBasicTypeLongLong, | |
| eBasicTypeShort, | |
| eBasicTypeSignedChar, | |
| eBasicTypeUnsignedChar, | |
| eBasicTypeUnsignedLong, | |
| eBasicTypeUnsignedLongLong, | |
| eBasicTypeUnsignedShort, | |
| eFormatChar, | |
| eTypeIsInteger, | |
| ) | |
| from rust_types import is_tuple_fields | |
| if TYPE_CHECKING: | |
| from lldb import SBProcess, SBTarget, SBType, SBTypeStaticField, SBValue | |
| # from lldb.formatters import Logger | |
| #################################################################################################### | |
| # This file contains two kinds of pretty-printers: summary and synthetic. | |
| # | |
| # Important classes from LLDB module: | |
| # SBValue: the value of a variable, a register, or an expression | |
| # SBType: the data type; each SBValue has a corresponding SBType | |
| # | |
| # Summary provider is a function with the type `(SBValue, dict) -> str`. | |
| # The first parameter is the object encapsulating the actual variable being displayed; | |
| # The second parameter is an internal support parameter used by LLDB, and you should not touch it. | |
| # | |
| # Synthetic children is the way to provide a children-based representation of the object's value. | |
| # Synthetic provider is a class that implements the following interface: | |
| # | |
| # class SyntheticChildrenProvider: | |
| # def __init__(self, SBValue, dict) | |
| # def num_children(self) | |
| # def get_child_index(self, str) | |
| # def get_child_at_index(self, int) | |
| # def update(self) | |
| # def has_children(self) | |
| # def get_value(self) | |
| # | |
| # | |
| # You can find more information and examples here: | |
| # 1. https://lldb.llvm.org/varformats.html | |
| # 2. https://lldb.llvm.org/use/python-reference.html | |
| # 3. https://github.com/llvm/llvm-project/blob/llvmorg-8.0.1/lldb/www/python_reference/lldb.formatters.cpp-pysrc.html | |
| # 4. https://github.com/llvm-mirror/lldb/tree/master/examples/summaries/cocoa | |
| #################################################################################################### | |
| PY3 = sys.version_info[0] == 3 | |
| class LLDBFeature(Flag): | |
| """Used to track which features we rely on and whether or not we can access them. The global | |
| `lldb_providers.FEATURE_FLAGS` is initialized in `lldb_lookup.__lldb_init_module` and is | |
| expected not to change after that point. | |
| This is used rather than `debugger.GetVersionString` because Apple's fork of LLDB (used for | |
| xcode) uses a non-standard versioning scheme that has no relation to LLVM's. | |
| """ | |
| StaticFields = auto() | |
| """Added in LLDB 18. Adds functions to `SBType` the inspection of a struct's static fields.""" | |
| TypeRecognizers = auto() | |
| """Added in LLDB 19. Callback-based type matching for synthetic/summary providers.""" | |
| Float128 = auto() | |
| """Added in LLDB 22.1. Adds builtin support for Float 128's, including an `eBasicTypeFloat128`, | |
| a formatter, and handlers in `TypeSystemClang`""" | |
| GetParent = auto() | |
| """Added in LLDB 23.1. Adds `SBValue.GetParent`, which retrieves the `SBValue` that the caller | |
| originates from. Useful when a child object must be modified/styled based on information only | |
| available to is parent e.g. unsized array types that must determine their length via the parent | |
| wide pointer value.""" | |
| ProviderDecorator = auto() | |
| """Added in LLDB 23.1. Adds `@lldb.summary` and `@lldb.synthetic`, which can automatically | |
| register decorated providers. At time of writing, we do not use this feature for the following | |
| reasons: | |
| 1. backwards compatibility | |
| 2. to maintain more strict control over the order in which providers are loaded""" | |
| PerObjectSynthetics = auto() | |
| """Currently only available in prerelease. Adds: | |
| * `SBValue.SetTypeSynthetic` - allows synthetic providers to override their children's synthetic | |
| provider without overriding the synthetic provider of all objects with that share a type name. | |
| * `SBValue.GetTypeSyntheticImplementation` - retrieves the *instance* of the synthetic provider | |
| associated with that variable. This allows us to easily inspect the state of a parent/child | |
| and use it to make decisions about the current object without needing to redo work. It is worth | |
| noting that this can be achieved backwards-compatibly (though less elegantly) by using a global | |
| `weakref.WeakValueDictionary`, with the keys being `SBValue.GetID()` (which are unique per | |
| session) and the values being the provider instance.""" | |
| def detect_features() -> LLDBFeature: | |
| import lldb | |
| features = LLDBFeature(0) | |
| # Most feature checks should be possible via simple "does this API exist at all" checks. | |
| if getattr(lldb.SBType, "GetStaticFieldWithName", None) is not None: | |
| features |= LLDBFeature.StaticFields | |
| if getattr(lldb, "eFormatterMatchCallback", None) is not None: | |
| features |= LLDBFeature.TypeRecognizers | |
| if getattr(lldb, "eBasicTypeFloat128", None) is not None: | |
| features |= LLDBFeature.Float128 | |
| if getattr(lldb.SBValue, "GetParent", None) is not None: | |
| features |= LLDBFeature.GetParent | |
| if getattr(lldb, "summary", None) is not None: | |
| features |= LLDBFeature.ProviderDecorator | |
| if getattr(lldb.SBValue, "SetTypeSynthetic", None) is not None: | |
| features |= LLDBFeature.PerObjectSynthetics | |
| return features | |
| FEATURE_FLAGS: LLDBFeature = detect_features() | |
| class LLDBOpaque: | |
| """ | |
| An marker type for use in type hints to denote LLDB bookkeeping variables. Values marked with | |
| this type should never be used except when passing as an argument to an LLDB function. | |
| """ | |
| class ValueBuilder: | |
| def __init__(self, valobj: SBValue): | |
| self.valobj = valobj | |
| process = valobj.GetProcess() | |
| self.endianness = process.GetByteOrder() | |
| self.pointer_size = process.GetAddressByteSize() | |
| def from_int(self, name: str, value: int) -> SBValue: | |
| type = self.valobj.GetType().GetBasicType(eBasicTypeLong) | |
| data = SBData.CreateDataFromSInt64Array( | |
| self.endianness, | |
| self.pointer_size, | |
| [value], | |
| ) | |
| return self.valobj.CreateValueFromData(name, data, type) | |
| def from_uint(self, name: str, value: int) -> SBValue: | |
| type = self.valobj.GetType().GetBasicType(eBasicTypeUnsignedLong) | |
| data = SBData.CreateDataFromUInt64Array( | |
| self.endianness, | |
| self.pointer_size, | |
| [value], | |
| ) | |
| return self.valobj.CreateValueFromData(name, data, type) | |
| def unwrap_unique_or_non_null(unique_or_nonnull: SBValue) -> SBValue: | |
| # BACKCOMPAT: rust 1.32 | |
| # https://github.com/rust-lang/rust/commit/7a0911528058e87d22ea305695f4047572c5e067 | |
| # BACKCOMPAT: rust 1.60 | |
| # https://github.com/rust-lang/rust/commit/2a91eeac1a2d27dd3de1bf55515d765da20fd86f | |
| ptr = unique_or_nonnull.GetChildMemberWithName("pointer") | |
| return ptr if ptr.TypeIsPointerType() else ptr.GetChildAtIndex(0) | |
| def unwrap_scalar_wrappers(wrapper: SBValue) -> SBValue: | |
| while (wrapper.type.GetTypeFlags() & eTypeIsInteger) == 0: | |
| wrapper = wrapper.GetChildAtIndex(0) | |
| return wrapper | |
| class DefaultSyntheticProvider: | |
| def __init__(self, valobj: SBValue, _dict: LLDBOpaque): | |
| # logger = Logger.Logger() | |
| # logger >> "Default synthetic provider for " + str(valobj.GetName()) | |
| self.valobj = valobj | |
| self.is_ptr = valobj.GetType().IsPointerType() | |
| def num_children(self) -> int: | |
| return self.valobj.GetNumChildren() | |
| def get_child_index(self, name: str) -> int: | |
| return self.valobj.GetIndexOfChildWithName(name) | |
| def get_child_at_index(self, index: int) -> Optional[SBValue]: | |
| return self.valobj.GetChildAtIndex(index) | |
| def update(self): | |
| pass | |
| def has_children(self) -> bool: | |
| return self.valobj.MightHaveChildren() | |
| def get_value(self): | |
| return self.valobj.value | |
| class EmptySyntheticProvider: | |
| def __init__(self, valobj: SBValue, _dict: LLDBOpaque): | |
| # logger = Logger.Logger() | |
| # logger >> "[EmptySyntheticProvider] for " + str(valobj.GetName()) | |
| self.valobj = valobj | |
| def num_children(self) -> int: | |
| return 0 | |
| def get_child_index(self, name: str) -> int: | |
| return -1 | |
| def get_child_at_index(self, index: int) -> Optional[SBValue]: | |
| return None | |
| def update(self): | |
| pass | |
| def has_children(self) -> bool: | |
| return False | |
| MSVC_STR_NAMES: List[str] = [ | |
| "ref$<str$>", | |
| "ref_mut$<str$>", | |
| "ptr_const$<str$>", | |
| "ptr_mut$<str$>", | |
| ] | |
| def get_template_args(type_name: str) -> Generator[str, None, None]: | |
| """ | |
| Takes a type name `T<A, tuple$<B, C>, D>` and returns a list of its generic args | |
| `["A", "tuple$<B, C>", "D"]`. | |
| Always returns an empty generator for `&str`, `&mut str`, `*const str`, and `*mut str` | |
| Strips off `enum2$<>` wrapper from enum types before checking for template args | |
| String-based replacement for LLDB's `SBType.template_args`, as LLDB is currently unable to | |
| populate this field for targets with PDB debug info. Also useful for manually altering the type | |
| name of generics (e.g. `Vec<ref$<str$> >` -> `Vec<&str>`). | |
| Each element of the returned list can be looked up for its `SBType` value via | |
| `SBTarget.FindFirstType()` | |
| """ | |
| if type_name in MSVC_STR_NAMES: | |
| return | |
| if type_name.startswith(("enum2$<", "slice2$<")): | |
| # remove the prefix and the trailing ">" | |
| type_name = type_name.split("<", 1)[0][:-1].strip() | |
| level = 0 | |
| start = 0 | |
| for i, c in enumerate(type_name): | |
| if c == "<": | |
| level += 1 | |
| if level == 1: | |
| start = i + 1 | |
| elif c == ">": | |
| level -= 1 | |
| if level == 0: | |
| yield type_name[start:i].strip() | |
| elif c == "," and level == 1: | |
| yield type_name[start:i].strip() | |
| start = i + 1 | |
| MSVC_PTR_PREFIX = ("ref$<", "ref_mut$<", "ptr_const$<", "ptr_mut$<") | |
| PRIMITIVE_TYPES: Dict[str, int] = { | |
| "u8": eBasicTypeUnsignedChar, | |
| "u16": eBasicTypeUnsignedShort, | |
| "u32": eBasicTypeUnsignedLong, | |
| "u64": eBasicTypeUnsignedLongLong, | |
| "i8": eBasicTypeSignedChar, | |
| "i16": eBasicTypeShort, | |
| "i32": eBasicTypeLong, | |
| "i64": eBasicTypeLongLong, | |
| "f16": eBasicTypeHalf, | |
| "f32": eBasicTypeFloat, | |
| "f64": eBasicTypeDouble, | |
| "char": eBasicTypeChar32, | |
| } | |
| def resolve_msvc_template_arg(arg_name: str, target: SBTarget) -> SBType: | |
| """ | |
| RECURSIVE when arrays or references are nested (e.g. `ref$<ref$<u8> >`, `array$<ref$<u8> >`) | |
| Takes the template arg's name (likely from `get_template_args`) and finds/creates its | |
| corresponding SBType. | |
| For non-reference/pointer/array types this is identical to calling | |
| `target.FindFirstType(arg_name)` | |
| LLDB internally interprets refs, pointers, and arrays C-style (`&u8` -> `u8 *`, | |
| `*const u8` -> `u8 *`, `[u8; 5]` -> `u8 [5]`). Looking up these names still doesn't work in the | |
| current version of LLDB, so instead the types are generated via `base_type.GetPointerType()` and | |
| `base_type.GetArrayType()`, which bypass the PDB file and ask clang directly for the type node. | |
| """ | |
| result = target.FindFirstType(arg_name) | |
| if result.IsValid(): | |
| return result | |
| if arg_name in MSVC_STR_NAMES: | |
| return target.FindFirstType(arg_name) | |
| # As of LLDB 22, finding primitives based on `FindFirstType` with their rust name no longer | |
| # works. Instead, we can look them up by their `eBasicType` equivalent. For usize and isize, | |
| # we convert them to their bit-sized counterpart before the lookup | |
| if arg_name == "isize" or arg_name == "usize": | |
| equivalent = f"{arg_name[0]}{target.GetAddressByteSize() * 8}" | |
| return target.GetBasicType(PRIMITIVE_TYPES[equivalent]) | |
| if (basic_type := PRIMITIVE_TYPES.get(arg_name)) is not None: | |
| return target.GetBasicType(basic_type) | |
| if arg_name == "f128" and LLDBFeature.Float128 in FEATURE_FLAGS: | |
| from lldb import eBasicTypeFloat128 | |
| return target.GetBasicType(eBasicTypeFloat128) | |
| for prefix in MSVC_PTR_PREFIX: | |
| if arg_name.startswith(prefix): | |
| arg_name = arg_name[len(prefix) : -1].strip() | |
| result = resolve_msvc_template_arg(arg_name, target) | |
| return result.GetPointerType() | |
| if arg_name.startswith("slice2$<"): | |
| arg_name = arg_name[len("slice2$<") : -1].strip() | |
| return resolve_msvc_template_arg(arg_name, target) | |
| if arg_name.startswith("array$<"): | |
| template_args = get_template_args(arg_name) | |
| element_name = next(template_args) | |
| length = next(template_args) | |
| result = resolve_msvc_template_arg(element_name, target) | |
| return result.GetArrayType(int(length)) | |
| return result | |
| def StructSummaryProvider(valobj: SBValue, _dict: LLDBOpaque) -> str: | |
| # structs need the field name before the field value | |
| output = ( | |
| f"{valobj.GetChildAtIndex(i).GetName()}:{child}" | |
| for i, child in enumerate(aggregate_field_summary(valobj, _dict)) | |
| ) | |
| return "{" + ", ".join(output) + "}" | |
| def TupleSummaryProvider(valobj: SBValue, _dict: LLDBOpaque): | |
| return "(" + ", ".join(aggregate_field_summary(valobj, _dict)) + ")" | |
| def aggregate_field_summary(valobj: SBValue, _dict) -> Generator[str, None, None]: | |
| for i in range(0, valobj.GetNumChildren()): | |
| child: SBValue = valobj.GetChildAtIndex(i) | |
| summary = child.summary | |
| if summary is None: | |
| summary = child.value | |
| if summary is None: | |
| if is_tuple_fields(child.GetType().fields): | |
| summary = TupleSummaryProvider(child, _dict) | |
| else: | |
| summary = StructSummaryProvider(child, _dict) | |
| yield summary | |
| def SizeSummaryProvider(valobj: SBValue, _dict: LLDBOpaque) -> str: | |
| return "size=" + str(valobj.GetNumChildren()) | |
| def vec_to_string(vec: SBValue) -> str: | |
| length = vec.GetNumChildren() | |
| chars = [vec.GetChildAtIndex(i).GetValueAsUnsigned() for i in range(length)] | |
| return ( | |
| bytes(chars).decode(errors="replace") | |
| if PY3 | |
| else "".join(chr(char) for char in chars) | |
| ) | |
| def read_string( | |
| process: SBProcess, address: int, length: int, error: Optional[SBError] = None | |
| ) -> str: | |
| """Reads a string from running process's memory. If `error` is passed in, it will be passed | |
| to the `SBProcess.ReadMemory` call, and will reflect any errors after the function is called. | |
| If any error or exception occurs, a placeholder byte array of the form "<error: [reason]>" will | |
| be returned instead.""" | |
| if error is None: | |
| error = SBError() | |
| try: | |
| data = process.ReadMemory(address, length, error) | |
| if error.Success(): | |
| return '"' + data.decode("utf-8", "replace") + '"' | |
| else: | |
| return f"<error: {error.GetCString()}>" | |
| except Exception as e: | |
| print(f"Unable to generate String summary: {e.__cause__}") | |
| return "<error: Unable to read memory>" | |
| def StdStringSummaryProvider(valobj: SBValue, dict: LLDBOpaque): | |
| inner_vec = ( | |
| valobj.GetNonSyntheticValue() | |
| .GetChildMemberWithName("vec") | |
| .GetNonSyntheticValue() | |
| ) | |
| pointer = unwrap_unique_or_non_null( | |
| inner_vec.GetChildMemberWithName("buf") | |
| .GetChildMemberWithName("inner") | |
| .GetChildMemberWithName("ptr") | |
| ) | |
| length = inner_vec.GetChildMemberWithName("len").GetValueAsUnsigned() | |
| capacity = ( | |
| inner_vec.GetChildMemberWithName("buf") | |
| .GetChildMemberWithName("cap") | |
| .GetValueAsUnsigned() | |
| ) | |
| if length <= 0: | |
| return '""' | |
| no_hi_bit_max: int = 1 << ((pointer.GetByteSize() * 8) - 1) | |
| # technically length isn't a NoHighBit<usize>, but length should always be <= capacity | |
| if length >= no_hi_bit_max or capacity >= no_hi_bit_max: | |
| return "<error: invalid len/capacity>" | |
| if pointer.GetValueAsUnsigned() == 0: | |
| return "<error: String pointer is null>" | |
| process = pointer.GetProcess() | |
| return read_string(process, pointer.GetValueAsAddress(), length) | |
| def StdOsStringSummaryProvider(valobj: SBValue, _dict: LLDBOpaque) -> str: | |
| inner_vec = valobj.GetNonSyntheticValue().GetChildAtIndex(0).GetChildAtIndex(0) | |
| is_windows = inner_vec.GetTypeName().endswith("Wtf8Buf") | |
| if is_windows: | |
| inner_vec = inner_vec.GetChildAtIndex(0) | |
| pointer = unwrap_unique_or_non_null( | |
| inner_vec.GetChildMemberWithName("buf") | |
| .GetChildMemberWithName("inner") | |
| .GetChildMemberWithName("ptr") | |
| ) | |
| length = inner_vec.GetChildMemberWithName("len").GetValueAsUnsigned() | |
| capacity = ( | |
| inner_vec.GetChildMemberWithName("buf") | |
| .GetChildMemberWithName("cap") | |
| .GetValueAsUnsigned() | |
| ) | |
| if length <= 0: | |
| return '""' | |
| no_hi_bit_max: int = 1 << ((pointer.GetByteSize() * 8) - 1) | |
| # technically length isn't a NoHighBit<usize>, but length should always be <= capacity | |
| if length >= no_hi_bit_max or capacity >= no_hi_bit_max: | |
| return "<error: invalid len/capacity>" | |
| if pointer.GetValueAsUnsigned() == 0: | |
| return "<error: OsString pointer is null>" | |
| process = pointer.GetProcess() | |
| return read_string(process, pointer.GetValueAsAddress(), length) | |
| def StdStrSummaryProvider(valobj: SBValue, _dict: LLDBOpaque) -> str: | |
| # logger = Logger.Logger() | |
| # logger >> "[StdStrSummaryProvider] for " + str(valobj.GetName()) | |
| # the code below assumes non-synthetic value, this makes sure the assumption holds | |
| valobj = valobj.GetNonSyntheticValue() | |
| length = valobj.GetChildMemberWithName("length").GetValueAsUnsigned() | |
| if length == 0: | |
| return '""' | |
| data_ptr = valobj.GetChildMemberWithName("data_ptr") | |
| start = data_ptr.GetValueAsUnsigned() | |
| error = SBError() | |
| process = data_ptr.GetProcess() | |
| data = process.ReadMemory(start, length, error) | |
| data = data.decode(encoding="UTF-8") if PY3 else data | |
| return '"%s"' % data | |
| def StdPathBufSummaryProvider(valobj: SBValue, _dict: LLDBOpaque) -> str: | |
| # logger = Logger.Logger() | |
| # logger >> "[StdPathBufSummaryProvider] for " + str(valobj.GetName()) | |
| return StdOsStringSummaryProvider(valobj.GetChildMemberWithName("inner"), _dict) | |
| def StdPathSummaryProvider(valobj: SBValue, _dict: LLDBOpaque) -> str: | |
| # logger = Logger.Logger() | |
| # logger >> "[StdPathSummaryProvider] for " + str(valobj.GetName()) | |
| length = valobj.GetChildMemberWithName("length").GetValueAsUnsigned() | |
| if length == 0: | |
| return '""' | |
| data_ptr = valobj.GetChildMemberWithName("data_ptr") | |
| start = data_ptr.GetValueAsUnsigned() | |
| process = data_ptr.GetProcess() | |
| return read_string(process, start, length) | |
| def f16SummaryProvider(valobj: SBValue, _dict: LLDBOpaque) -> str: | |
| return ( | |
| valobj.GetChildAtIndex(0) | |
| .Cast(valobj.GetTarget().GetBasicType(eBasicTypeHalf)) | |
| .GetValue() | |
| ) | |
| def f128SummaryProvider(valobj: SBValue, _dict: LLDBOpaque) -> str: | |
| from lldb import eBasicTypeFloat128 | |
| return valobj.Cast(valobj.GetTarget().GetBasicType(eBasicTypeFloat128)).GetValue() | |
| def sequence_formatter(output: str, valobj: SBValue, _dict: LLDBOpaque): | |
| length: int = valobj.GetNumChildren() | |
| if length == 0: | |
| return output | |
| long: bool = False | |
| for i in range(0, length): | |
| if len(output) > 32: | |
| long = True | |
| break | |
| child: SBValue = valobj.GetChildAtIndex(i) | |
| summary = child.summary | |
| if summary is None: | |
| summary = child.value | |
| if summary is None: | |
| summary = "{...}" | |
| output += f"{summary}, " | |
| if long: | |
| output = f"(len: {length}) " + output + "..." | |
| else: | |
| output = output[:-2] | |
| return output | |
| class StructSyntheticProvider: | |
| """Pretty-printer for structs and struct enum variants""" | |
| def __init__(self, valobj: SBValue, _dict: LLDBOpaque, is_variant: bool = False): | |
| # logger = Logger.Logger() | |
| self.valobj = valobj | |
| self.is_variant = is_variant | |
| self.type = valobj.GetType() | |
| self.fields = {} | |
| if is_variant: | |
| self.fields_count = self.type.GetNumberOfFields() - 1 | |
| real_fields = self.type.fields[1:] | |
| else: | |
| self.fields_count = self.type.GetNumberOfFields() | |
| real_fields = self.type.fields | |
| for number, field in enumerate(real_fields): | |
| self.fields[field.name] = number | |
| def num_children(self) -> int: | |
| return self.fields_count | |
| def get_child_index(self, name: str) -> int: | |
| return self.fields.get(name, -1) | |
| def get_child_at_index(self, index: int) -> Optional[SBValue]: | |
| if self.is_variant: | |
| field = self.type.GetFieldAtIndex(index + 1) | |
| else: | |
| field = self.type.GetFieldAtIndex(index) | |
| return self.valobj.GetChildMemberWithName(field.name) | |
| def update(self): | |
| # type: () -> None | |
| pass | |
| def has_children(self) -> bool: | |
| return True | |
| class StdStringSyntheticProvider: | |
| def __init__(self, valobj: SBValue, _dict: LLDBOpaque): | |
| self.valobj = valobj | |
| ptr_size = valobj.GetTarget().GetAddressByteSize() * 8 | |
| self.no_hi_bit_max = 1 << (ptr_size - 1) | |
| self.update() | |
| def update(self): | |
| inner_vec = self.valobj.GetChildMemberWithName("vec").GetNonSyntheticValue() | |
| self.data_ptr = unwrap_unique_or_non_null( | |
| inner_vec.GetChildMemberWithName("buf") | |
| .GetChildMemberWithName("inner") | |
| .GetChildMemberWithName("ptr") | |
| ) | |
| self.capacity = ( | |
| inner_vec.GetChildMemberWithName("buf") | |
| .GetChildMemberWithName("cap") | |
| .GetValueAsUnsigned() | |
| ) | |
| # As of 4/18/2026, LLDB cannot accurately determine the difference between Some("") and None | |
| # this just makes sure we're not trying to access data when the string is clearly in an | |
| # invalid state. | |
| if ( | |
| self.capacity >= self.no_hi_bit_max | |
| or self.data_ptr.GetValueAsUnsigned() == 0 | |
| ): | |
| self.capacity = 0 | |
| self.length = 0 | |
| else: | |
| self.length = inner_vec.GetChildMemberWithName("len").GetValueAsUnsigned() | |
| self.element_type = self.data_ptr.GetType().GetPointeeType() | |
| def has_children(self) -> bool: | |
| return True | |
| def num_children(self) -> int: | |
| return self.length | |
| def get_child_index(self, name: str) -> int: | |
| index = name.lstrip("[").rstrip("]") | |
| if index.isdigit(): | |
| return int(index) | |
| return -1 | |
| def get_child_at_index(self, index: int) -> Optional[SBValue]: | |
| if not 0 <= index < self.length: | |
| return None | |
| start = self.data_ptr.GetValueAsUnsigned() | |
| address = start + index | |
| element = self.data_ptr.CreateValueFromAddress( | |
| f"[{index}]", address, self.element_type | |
| ) | |
| element.SetFormat(eFormatChar) | |
| return element | |
| class MSVCStrSyntheticProvider: | |
| _name_map: Dict[str, str] = { | |
| "ref$<str$>": "&str", | |
| "ref_mut$<str$>": "&mut str", | |
| "ptr_const$<str$>": "*const str", | |
| "ptr_mut$<str$>": "*mut str", | |
| } | |
| __slots__ = ["data_ptr", "length", "valobj"] | |
| def __init__(self, valobj: SBValue, _dict: LLDBOpaque): | |
| self.valobj = valobj | |
| self.update() | |
| def update(self): | |
| self.data_ptr = self.valobj.GetChildMemberWithName("data_ptr") | |
| self.length = self.valobj.GetChildMemberWithName("length").GetValueAsUnsigned() | |
| def has_children(self) -> bool: | |
| return True | |
| def num_children(self) -> int: | |
| return self.length | |
| def get_child_index(self, name: str) -> int: | |
| index = name.lstrip("[").rstrip("]") | |
| if index.isdigit(): | |
| return int(index) | |
| return -1 | |
| def get_child_at_index(self, index: int) -> Optional[SBValue]: | |
| if not 0 <= index < self.length: | |
| return None | |
| start = self.data_ptr.GetValueAsUnsigned() | |
| address = start + index | |
| element = self.data_ptr.CreateValueFromAddress( | |
| f"[{index}]", address, self.data_ptr.GetType().GetPointeeType() | |
| ) | |
| element.SetFormat(eFormatChar) | |
| return element | |
| def get_type_name(self): | |
| name = self.valobj.GetTypeName() | |
| if (type_name := self._name_map.get(name)) is not None: | |
| return type_name | |
| elif name.startswith("alloc::boxed::Box<str$"): | |
| return "Box<str>" | |
| else: | |
| return name | |
| def _getVariantName(variant: SBValue) -> str: | |
| """ | |
| Since the enum variant's type name is in the form `TheEnumName::TheVariantName$Variant`, | |
| we can extract `TheVariantName` from it for display purpose. | |
| """ | |
| s = variant.GetType().GetName() | |
| if not s.endswith("$Variant"): | |
| return "" | |
| # trim off path and "$Variant" | |
| # len("$Variant") == 8 | |
| return s.rsplit("::", 1)[1][:-8] | |
| class ClangEncodedEnumProvider: | |
| """Pretty-printer for 'clang-encoded' enums support implemented in LLDB""" | |
| valobj: SBValue | |
| variant: SBValue | |
| value: SBValue | |
| DISCRIMINANT_MEMBER_NAME = "$discr$" | |
| VALUE_MEMBER_NAME = "value" | |
| __slots__ = ("valobj", "variant", "value") | |
| def __init__(self, valobj: SBValue, _dict: LLDBOpaque): | |
| self.valobj = valobj | |
| self.update() | |
| def has_children(self) -> bool: | |
| return self.value.MightHaveChildren() | |
| def num_children(self) -> int: | |
| return self.value.GetNumChildren() | |
| def get_child_index(self, name: str) -> int: | |
| return self.value.GetIndexOfChildWithName(name) | |
| def get_child_at_index(self, index: int) -> Optional[SBValue]: | |
| return self.value.GetChildAtIndex(index) | |
| def update(self): | |
| all_variants = self.valobj.GetChildAtIndex(0) | |
| index = self._getCurrentVariantIndex(all_variants) | |
| self.variant = all_variants.GetChildAtIndex(index) | |
| self.value = self.variant.GetChildMemberWithName( | |
| ClangEncodedEnumProvider.VALUE_MEMBER_NAME | |
| ) | |
| if (synth := self.value.GetSyntheticValue()).IsValid(): | |
| self.value = synth | |
| def _getCurrentVariantIndex(self, all_variants: SBValue) -> int: | |
| default_index = 0 | |
| for i in range(all_variants.GetNumChildren()): | |
| variant = all_variants.GetChildAtIndex(i) | |
| discr = variant.GetChildMemberWithName( | |
| ClangEncodedEnumProvider.DISCRIMINANT_MEMBER_NAME | |
| ) | |
| if discr.IsValid(): | |
| discr_unsigned_value = discr.GetValueAsUnsigned() | |
| if variant.GetName() == f"$variant${discr_unsigned_value}": | |
| return i | |
| else: | |
| default_index = i | |
| return default_index | |
| def ClangEncodedEnumSummaryProvider(valobj: SBValue, _dict: LLDBOpaque) -> str: | |
| if valobj.TypeIsPointerType(): | |
| valobj = valobj.Dereference() | |
| enum_synth = ClangEncodedEnumProvider(valobj.GetNonSyntheticValue(), _dict) | |
| variant = enum_synth.variant | |
| name = _getVariantName(variant) | |
| if valobj.GetNumChildren() == 0: | |
| return name | |
| child_name: str = valobj.GetChildAtIndex(0).name | |
| if child_name == "0" or child_name == "__0": | |
| # enum variant is a tuple struct | |
| return name + TupleSummaryProvider(valobj, _dict) | |
| else: | |
| # enum variant is a regular struct | |
| return name + StructSummaryProvider(valobj, _dict) | |
| class MSVCEnumSyntheticProvider: | |
| """ | |
| Synthetic provider for sum-type enums on MSVC. For a detailed explanation of the internals, | |
| see: | |
| https://github.com/rust-lang/rust/blob/HEAD/compiler/rustc_codegen_llvm/src/debuginfo/metadata/enums/cpp_like.rs | |
| """ | |
| valobj: SBValue | |
| variant: SBValue | |
| value: SBValue | |
| __slots__ = ["valobj", "variant", "value"] | |
| def __init__(self, valobj: SBValue, _dict: LLDBOpaque): | |
| self.valobj = valobj | |
| # This allows the summary provider to still print something | |
| # even if we can't find the variant for whatever reason | |
| self.variant = valobj | |
| self.value = valobj | |
| self.update() | |
| def update(self): | |
| tag: SBValue = self.valobj.GetChildMemberWithName("tag") | |
| if tag.IsValid(): | |
| tag: int = tag.GetValueAsUnsigned() | |
| for child in self.valobj.GetNonSyntheticValue().children: | |
| if not child.name.startswith("variant"): | |
| continue | |
| variant_type: SBType = child.GetType() | |
| try: | |
| exact: SBTypeStaticField = variant_type.GetStaticFieldWithName( | |
| "DISCR_EXACT" | |
| ) | |
| except AttributeError: | |
| # LLDB versions prior to 19.0.0 do not have the `SBTypeGetStaticField` API. | |
| # With current DI generation there's not a great way to provide a "best effort" | |
| # evaluation either, so we just return the object itself with no further | |
| # attempts to inspect the type information | |
| self.variant = self.valobj | |
| self.value = self.valobj | |
| return | |
| if exact.IsValid(): | |
| discr: int = exact.GetConstantValue( | |
| self.valobj.target | |
| ).GetValueAsUnsigned() | |
| if tag == discr: | |
| self.variant = child | |
| self.value = child.GetChildMemberWithName("value") | |
| if (synth := self.value.GetSyntheticValue()).IsValid(): | |
| self.value = synth | |
| return | |
| else: # if invalid, DISCR must be a range | |
| begin: int = ( | |
| variant_type.GetStaticFieldWithName("DISCR_BEGIN") | |
| .GetConstantValue(self.valobj.target) | |
| .GetValueAsUnsigned() | |
| ) | |
| end: int = ( | |
| variant_type.GetStaticFieldWithName("DISCR_END") | |
| .GetConstantValue(self.valobj.target) | |
| .GetValueAsUnsigned() | |
| ) | |
| # begin isn't necessarily smaller than end, so we must test for both cases | |
| if begin < end: | |
| if begin <= tag <= end: | |
| self.variant = child | |
| self.value = child.GetChildMemberWithName("value") | |
| if (synth := self.value.GetSyntheticValue()).IsValid(): | |
| self.value = synth | |
| return | |
| else: | |
| if tag >= begin or tag <= end: | |
| self.variant = child | |
| self.value = child.GetChildMemberWithName("value") | |
| if (synth := self.value.GetSyntheticValue()).IsValid(): | |
| self.value = synth | |
| return | |
| else: # if invalid, tag is a 128 bit value | |
| tag_lo: int = self.valobj.GetChildMemberWithName( | |
| "tag128_lo" | |
| ).GetValueAsUnsigned() | |
| tag_hi: int = self.valobj.GetChildMemberWithName( | |
| "tag128_hi" | |
| ).GetValueAsUnsigned() | |
| tag: int = (tag_hi << 64) | tag_lo | |
| for child in self.valobj.GetNonSyntheticValue().children: | |
| if not child.name.startswith("variant"): | |
| continue | |
| variant_type: SBType = child.GetType() | |
| exact_lo: SBTypeStaticField = variant_type.GetStaticFieldWithName( | |
| "DISCR128_EXACT_LO" | |
| ) | |
| if exact_lo.IsValid(): | |
| exact_lo: int = exact_lo.GetConstantValue( | |
| self.valobj.target | |
| ).GetValueAsUnsigned() | |
| exact_hi: int = ( | |
| variant_type.GetStaticFieldWithName("DISCR128_EXACT_HI") | |
| .GetConstantValue(self.valobj.target) | |
| .GetValueAsUnsigned() | |
| ) | |
| discr: int = (exact_hi << 64) | exact_lo | |
| if tag == discr: | |
| self.variant = child | |
| self.value = child.GetChildMemberWithName("value") | |
| if (synth := self.value.GetSyntheticValue()).IsValid(): | |
| self.value = synth | |
| return | |
| else: # if invalid, DISCR must be a range | |
| begin_lo: int = ( | |
| variant_type.GetStaticFieldWithName("DISCR128_BEGIN_LO") | |
| .GetConstantValue(self.valobj.target) | |
| .GetValueAsUnsigned() | |
| ) | |
| begin_hi: int = ( | |
| variant_type.GetStaticFieldWithName("DISCR128_BEGIN_HI") | |
| .GetConstantValue(self.valobj.target) | |
| .GetValueAsUnsigned() | |
| ) | |
| end_lo: int = ( | |
| variant_type.GetStaticFieldWithName("DISCR128_END_LO") | |
| .GetConstantValue(self.valobj.target) | |
| .GetValueAsUnsigned() | |
| ) | |
| end_hi: int = ( | |
| variant_type.GetStaticFieldWithName("DISCR128_END_HI") | |
| .GetConstantValue(self.valobj.target) | |
| .GetValueAsUnsigned() | |
| ) | |
| begin = (begin_hi << 64) | begin_lo | |
| end = (end_hi << 64) | end_lo | |
| # begin isn't necessarily smaller than end, so we must test for both cases | |
| if begin < end: | |
| if begin <= tag <= end: | |
| self.variant = child | |
| self.value = child.GetChildMemberWithName("value") | |
| if (synth := self.value.GetSyntheticValue()).IsValid(): | |
| self.value = synth | |
| return | |
| else: | |
| if tag >= begin or tag <= end: | |
| self.variant = child | |
| self.value = child.GetChildMemberWithName("value") | |
| if (synth := self.value.GetSyntheticValue()).IsValid(): | |
| self.value = synth | |
| return | |
| def num_children(self) -> int: | |
| return self.value.GetNumChildren() | |
| def get_child_index(self, name: str) -> int: | |
| return self.value.GetIndexOfChildWithName(name) | |
| def get_child_at_index(self, index: int) -> Optional[SBValue]: | |
| return self.value.GetChildAtIndex(index) | |
| def has_children(self) -> bool: | |
| return self.value.MightHaveChildren() | |
| def get_type_name(self) -> str: | |
| name = self.valobj.GetTypeName() | |
| # remove "enum2$<", str.removeprefix() is python 3.9+ | |
| name = name[7:] | |
| # MSVC misinterprets ">>" as a shift operator, so spaces are inserted by rust to | |
| # avoid that | |
| if name.endswith(" >"): | |
| name = name[:-2] | |
| elif name.endswith(">"): | |
| name = name[:-1] | |
| return name | |
| def MSVCEnumSummaryProvider(valobj: SBValue, _dict: LLDBOpaque) -> str: | |
| if valobj.TypeIsPointerType(): | |
| valobj = valobj.Dereference() | |
| enum_synth = MSVCEnumSyntheticProvider(valobj.GetNonSyntheticValue(), _dict) | |
| variant_names: SBType = valobj.target.FindFirstType( | |
| f"{enum_synth.valobj.GetTypeName()}::VariantNames" | |
| ) | |
| try: | |
| name_idx = ( | |
| enum_synth.variant.GetType() | |
| .GetStaticFieldWithName("NAME") | |
| .GetConstantValue(valobj.target) | |
| .GetValueAsUnsigned() | |
| ) | |
| except AttributeError: | |
| # LLDB versions prior to 19 do not have the `SBTypeGetStaticField` API, and have no way | |
| # to determine the value based on the tag field. | |
| tag: SBValue = valobj.GetChildMemberWithName("tag") | |
| if tag.IsValid(): | |
| discr: int = tag.GetValueAsUnsigned() | |
| return "".join(["{tag = ", str(tag.unsigned), "}"]) | |
| else: | |
| tag_lo: int = valobj.GetChildMemberWithName( | |
| "tag128_lo" | |
| ).GetValueAsUnsigned() | |
| tag_hi: int = valobj.GetChildMemberWithName( | |
| "tag128_hi" | |
| ).GetValueAsUnsigned() | |
| discr: int = (tag_hi << 64) | tag_lo | |
| return "".join(["{tag = ", str(discr), "}"]) | |
| name: str = variant_names.enum_members[name_idx].name | |
| if enum_synth.num_children() == 0: | |
| return name | |
| child_name: str = enum_synth.value.GetChildAtIndex(0).name | |
| if child_name == "0" or child_name == "__0": | |
| # enum variant is a tuple struct | |
| return name + TupleSummaryProvider(enum_synth.value, _dict) | |
| else: | |
| # enum variant is a regular struct | |
| return name + StructSummaryProvider(enum_synth.value, _dict) | |
| class TupleSyntheticProvider: | |
| """Pretty-printer for tuples and tuple enum variants""" | |
| def __init__(self, valobj: SBValue, _dict: LLDBOpaque, is_variant: bool = False): | |
| # logger = Logger.Logger() | |
| self.valobj = valobj | |
| self.is_variant = is_variant | |
| self.type = valobj.GetType() | |
| if is_variant: | |
| self.size = self.type.GetNumberOfFields() - 1 | |
| else: | |
| self.size = self.type.GetNumberOfFields() | |
| def num_children(self) -> int: | |
| return self.size | |
| def get_child_index(self, name: str) -> int: | |
| if name.isdigit(): | |
| return self.valobj.GetIndexOfChildWithName(f"__{name}") | |
| else: | |
| return -1 | |
| def get_child_at_index(self, index: int) -> Optional[SBValue]: | |
| return self.valobj.GetChildAtIndex( | |
| self.valobj.GetIndexOfChildWithName(f"__{index}") | |
| ).Clone(str(index)) | |
| def update(self): | |
| pass | |
| def has_children(self) -> bool: | |
| return True | |
| class MSVCTupleSyntheticProvider: | |
| __slots__ = ["valobj"] | |
| def __init__(self, valobj: SBValue, _dict: LLDBOpaque): | |
| self.valobj = valobj | |
| def num_children(self) -> int: | |
| return self.valobj.GetNumChildren() | |
| def get_child_index(self, name: str) -> int: | |
| if name.isdigit(): | |
| return self.valobj.GetIndexOfChildWithName(f"__{name}") | |
| else: | |
| return -1 | |
| def get_child_at_index(self, index: int) -> Optional[SBValue]: | |
| return self.valobj.GetChildAtIndex( | |
| self.valobj.GetIndexOfChildWithName(f"__{index}") | |
| ).Clone(str(index)) | |
| def update(self): | |
| pass | |
| def has_children(self) -> bool: | |
| return self.valobj.MightHaveChildren() | |
| def get_type_name(self) -> str: | |
| name = self.valobj.GetTypeName() | |
| # remove "tuple$<" and ">", str.removeprefix and str.removesuffix require python 3.9+ | |
| name = name[7:-1].strip() | |
| return "(" + name + ")" | |
| class StdVecSyntheticProvider: | |
| """Pretty-printer for alloc::vec::Vec<T> | |
| struct Vec<T> { buf: RawVec<T>, len: usize } | |
| rust 1.75: struct RawVec<T> { ptr: Unique<T>, cap: usize, ... } | |
| rust 1.76: struct RawVec<T> { ptr: Unique<T>, cap: Cap(usize), ... } | |
| rust 1.31.1: struct Unique<T: ?Sized> { pointer: NonZero<*const T>, ... } | |
| rust 1.33.0: struct Unique<T: ?Sized> { pointer: *const T, ... } | |
| rust 1.62.0: struct Unique<T: ?Sized> { pointer: NonNull<T>, ... } | |
| struct NonZero<T>(T) | |
| struct NonNull<T> { pointer: *const T } | |
| FIXME: This version history has been incomplete for a long time. | |
| Maybe either update it to reflect all the version changes, | |
| including addition of RawVecInner and allocators, or just delete it entirely. | |
| """ | |
| def __init__(self, valobj: SBValue, _dict: LLDBOpaque): | |
| # logger = Logger.Logger() | |
| # logger >> "[StdVecSyntheticProvider] for " + str(valobj.GetName()) | |
| self.valobj = valobj | |
| self.element_type = None | |
| ptr_size = valobj.GetTarget().GetAddressByteSize() * 8 | |
| self.no_hi_bit_max = 1 << (ptr_size - 1) | |
| self.update() | |
| def num_children(self) -> int: | |
| return self.length | |
| def get_child_index(self, name: str) -> int: | |
| index = name.lstrip("[").rstrip("]") | |
| if index.isdigit(): | |
| return int(index) | |
| else: | |
| return -1 | |
| def get_child_at_index(self, index: int) -> Optional[SBValue]: | |
| start = self.data_ptr.GetValueAsUnsigned() | |
| address = start + index * self.element_type_size | |
| element = self.data_ptr.CreateValueFromAddress( | |
| "[%s]" % index, address, self.element_type | |
| ) | |
| return element | |
| def update(self): | |
| buf: SBValue = self.valobj.GetChildMemberWithName("buf") | |
| self.data_ptr = unwrap_unique_or_non_null( | |
| buf.GetChildMemberWithName("inner").GetChildMemberWithName("ptr") | |
| ) | |
| capacity: int = buf.GetChildMemberWithName("cap").GetValueAsUnsigned() | |
| if capacity >= self.no_hi_bit_max or self.data_ptr.GetValueAsUnsigned() == 0: | |
| self.capacity = 0 | |
| self.length = 0 | |
| else: | |
| self.length = self.valobj.GetChildMemberWithName("len").GetValueAsUnsigned() | |
| self.element_type = self.valobj.GetType().GetTemplateArgumentType(0) | |
| if not self.element_type.IsValid(): | |
| arg_name = next(get_template_args(self.valobj.GetTypeName())) | |
| self.element_type = resolve_msvc_template_arg(arg_name, self.valobj.target) | |
| self.element_type_size = self.element_type.GetByteSize() | |
| def has_children(self) -> bool: | |
| return True | |
| class StdSliceSyntheticProvider: | |
| __slots__ = [ | |
| "valobj", | |
| "length", | |
| "data_ptr", | |
| "element_type", | |
| "element_size", | |
| "is_str", | |
| ] | |
| def __init__(self, valobj: SBValue, _dict: LLDBOpaque): | |
| self.valobj = valobj | |
| type_name = self.valobj.GetTypeName() | |
| self.is_str = type_name.startswith("alloc::boxed::Box<str") or type_name in { | |
| "&str", | |
| "&mut str", | |
| "*const str", | |
| "*mut str", | |
| "ref$<str>", | |
| "ref_mut$<str>", | |
| "ptr_const$<str>", | |
| "ptr_mut$<str>", | |
| } | |
| self.update() | |
| def num_children(self) -> int: | |
| return self.length | |
| def get_child_index(self, name: str) -> int: | |
| index = name.lstrip("[").rstrip("]") | |
| if index.isdigit(): | |
| return int(index) | |
| else: | |
| return -1 | |
| def get_child_at_index(self, index: int) -> Optional[SBValue]: | |
| start = self.data_ptr.GetValueAsUnsigned() | |
| address = start + index * self.element_size | |
| element = self.data_ptr.CreateValueFromAddress( | |
| "[%s]" % index, address, self.element_type | |
| ) | |
| if self.is_str: | |
| element.SetFormat(eFormatChar) | |
| return element | |
| def update(self): | |
| self.length = self.valobj.GetChildMemberWithName("length").GetValueAsUnsigned() | |
| self.data_ptr = self.valobj.GetChildMemberWithName("data_ptr") | |
| self.element_type = self.data_ptr.GetType().GetPointeeType() | |
| self.element_size = self.element_type.GetByteSize() | |
| def has_children(self) -> bool: | |
| return True | |
| class MSVCStdSliceSyntheticProvider(StdSliceSyntheticProvider): | |
| type_name: Optional[str] = None | |
| def get_type_name(self) -> str: | |
| if self.type_name is not None: | |
| return self.type_name | |
| name = self.valobj.GetTypeName() | |
| if name.startswith("ref_mut"): | |
| name = name[len("ref_mut$<slice2$<") :].rstrip("> ") | |
| self.type_name = f"&mut [{name}]" | |
| elif name.startswith("ref"): | |
| name = name[len("ref$<slice2$<") :].rstrip("> ") | |
| self.type_name = f"&[{name}]" | |
| elif name.startswith("ptr_mut"): | |
| name = name[len("ptr_mut$<slice2$<") :].rstrip("> ") | |
| self.type_name = f"*mut [{name}]" | |
| elif name.startswith("ptr_const"): | |
| name = name[len("ptr_const$<slice2$<") :].rstrip("> ") | |
| self.type_name = f"*const [{name}]" | |
| elif name.startswith("alloc::boxed::Box"): | |
| prefix_len = len("alloc::boxed::Box<slice2$<") | |
| suffix_len = len(">,alloc::alloc::Global>") | |
| if name.endswith(",alloc::alloc::Global>"): | |
| name = name[prefix_len : len(name) - suffix_len] | |
| self.type_name = f"Box<[{name}]>" | |
| else: | |
| [element_name, alloc_name] = name[prefix_len:].split(">", 1) | |
| name = f"{element_name}{alloc_name}" | |
| # alloc name contains the trailing ">", so we don't need to add it | |
| self.type_name = f"Box<[{element_name}]{alloc_name}" | |
| else: | |
| self.type_name = name | |
| return self.type_name | |
| def StdSliceSummaryProvider(valobj, dict): | |
| output = sequence_formatter("[", valobj, dict) | |
| output += "]" | |
| return output | |
| class StdVecDequeSyntheticProvider: | |
| """Pretty-printer for alloc::collections::vec_deque::VecDeque<T> | |
| struct VecDeque<T> { head: WrappedIndex, len: usize, buf: RawVec<T> } | |
| """ | |
| def __init__(self, valobj: SBValue, _dict: LLDBOpaque): | |
| # logger = Logger.Logger() | |
| # logger >> "[StdVecDequeSyntheticProvider] for " + str(valobj.GetName()) | |
| self.valobj = valobj | |
| self.element_type = None | |
| self.update() | |
| def num_children(self) -> int: | |
| return self.size | |
| def get_child_index(self, name: str) -> int: | |
| index = name.lstrip("[").rstrip("]") | |
| if index.isdigit() and int(index) < self.size: | |
| return int(index) | |
| else: | |
| return -1 | |
| def get_child_at_index(self, index: int) -> Optional[SBValue]: | |
| start = self.data_ptr.GetValueAsUnsigned() | |
| address = start + ((index + self.head) % self.cap) * self.element_type_size | |
| element = self.data_ptr.CreateValueFromAddress( | |
| "[%s]" % index, address, self.element_type | |
| ) | |
| return element | |
| def update(self): | |
| head = self.valobj.GetChildMemberWithName("head") | |
| # BACKCOMPAT: rust 1.95 | |
| if head.GetType().num_fields == 1: | |
| head = head.GetChildAtIndex(0) | |
| self.head = head.GetValueAsUnsigned() | |
| self.size = self.valobj.GetChildMemberWithName("len").GetValueAsUnsigned() | |
| self.buf = self.valobj.GetChildMemberWithName("buf").GetChildMemberWithName( | |
| "inner" | |
| ) | |
| cap = self.buf.GetChildMemberWithName("cap") | |
| if cap.GetType().num_fields == 1: | |
| cap = cap.GetChildAtIndex(0) | |
| self.cap = cap.GetValueAsUnsigned() | |
| self.data_ptr = unwrap_unique_or_non_null( | |
| self.buf.GetChildMemberWithName("ptr") | |
| ) | |
| self.element_type = self.valobj.GetType().GetTemplateArgumentType(0) | |
| if not self.element_type.IsValid(): | |
| arg_name = next(get_template_args(self.valobj.GetTypeName())) | |
| self.element_type = resolve_msvc_template_arg(arg_name, self.valobj.target) | |
| self.element_type_size = self.element_type.GetByteSize() | |
| def has_children(self) -> bool: | |
| return True | |
| # BACKCOMPAT: rust 1.35 | |
| class StdOldHashMapSyntheticProvider: | |
| """Pretty-printer for std::collections::hash::map::HashMap<K, V, S> | |
| struct HashMap<K, V, S> {..., table: RawTable<K, V>, ... } | |
| struct RawTable<K, V> { capacity_mask: usize, size: usize, hashes: TaggedHashUintPtr, ... } | |
| """ | |
| def __init__(self, valobj: SBValue, _dict: LLDBOpaque, show_values: bool = True): | |
| self.valobj = valobj | |
| self.show_values = show_values | |
| self.update() | |
| def num_children(self) -> int: | |
| return self.size | |
| def get_child_index(self, name: str) -> int: | |
| index = name.lstrip("[").rstrip("]") | |
| if index.isdigit(): | |
| return int(index) | |
| else: | |
| return -1 | |
| def get_child_at_index(self, index: int) -> Optional[SBValue]: | |
| # logger = Logger.Logger() | |
| start = self.data_ptr.GetValueAsUnsigned() & ~1 | |
| # See `libstd/collections/hash/table.rs:raw_bucket_at | |
| hashes = self.hash_uint_size * self.capacity | |
| align = self.pair_type_size | |
| # See `libcore/alloc.rs:padding_needed_for` | |
| len_rounded_up = ( | |
| ( | |
| (((hashes + align) % self.modulo - 1) % self.modulo) | |
| & ~((align - 1) % self.modulo) | |
| ) | |
| % self.modulo | |
| - hashes | |
| ) % self.modulo | |
| # len_rounded_up = ((hashes + align - 1) & ~(align - 1)) - hashes | |
| pairs_offset = hashes + len_rounded_up | |
| pairs_start = start + pairs_offset | |
| table_index = self.valid_indices[index] | |
| idx = table_index & self.capacity_mask | |
| address = pairs_start + idx * self.pair_type_size | |
| element = self.data_ptr.CreateValueFromAddress( | |
| "[%s]" % index, address, self.pair_type | |
| ) | |
| if self.show_values: | |
| return element | |
| else: | |
| key = element.GetChildAtIndex(0) | |
| return self.valobj.CreateValueFromData( | |
| "[%s]" % index, key.GetData(), key.GetType() | |
| ) | |
| def update(self): | |
| # logger = Logger.Logger() | |
| self.table = self.valobj.GetChildMemberWithName("table") # type: SBValue | |
| self.size = self.table.GetChildMemberWithName("size").GetValueAsUnsigned() | |
| self.hashes = self.table.GetChildMemberWithName("hashes") | |
| self.hash_uint_type = self.hashes.GetType() | |
| self.hash_uint_size = self.hashes.GetType().GetByteSize() | |
| self.modulo = 2**self.hash_uint_size | |
| self.data_ptr = self.hashes.GetChildAtIndex(0).GetChildAtIndex(0) | |
| self.capacity_mask = self.table.GetChildMemberWithName( | |
| "capacity_mask" | |
| ).GetValueAsUnsigned() | |
| self.capacity = (self.capacity_mask + 1) % self.modulo | |
| marker = self.table.GetChildMemberWithName("marker").GetType() # type: SBType | |
| self.pair_type = marker.template_args[0] | |
| self.pair_type_size = self.pair_type.GetByteSize() | |
| self.valid_indices = [] | |
| for idx in range(self.capacity): | |
| address = self.data_ptr.GetValueAsUnsigned() + idx * self.hash_uint_size | |
| hash_uint = self.data_ptr.CreateValueFromAddress( | |
| "[%s]" % idx, address, self.hash_uint_type | |
| ) | |
| hash_ptr = hash_uint.GetChildAtIndex(0).GetChildAtIndex(0) | |
| if hash_ptr.GetValueAsUnsigned() != 0: | |
| self.valid_indices.append(idx) | |
| # logger >> "Valid indices: {}".format(str(self.valid_indices)) | |
| def has_children(self) -> bool: | |
| return True | |
| class StdHashMapSyntheticProvider: | |
| """Pretty-printer for hashbrown's HashMap""" | |
| def __init__(self, valobj: SBValue, _dict: LLDBOpaque, show_values: bool = True): | |
| self.valobj = valobj | |
| self.show_values = show_values | |
| self.update() | |
| def num_children(self) -> int: | |
| return self.size | |
| def get_child_index(self, name: str) -> int: | |
| index = name.lstrip("[").rstrip("]") | |
| if index.isdigit(): | |
| return int(index) | |
| else: | |
| return -1 | |
| def get_child_at_index(self, index: int) -> Optional[SBValue]: | |
| pairs_start = self.data_ptr.GetValueAsUnsigned() | |
| idx = self.valid_indices[index] | |
| if self.new_layout: | |
| idx = -(idx + 1) | |
| address = pairs_start + idx * self.pair_type_size | |
| element = self.data_ptr.CreateValueFromAddress( | |
| "[%s]" % index, address, self.pair_type | |
| ) | |
| if self.show_values: | |
| return element | |
| else: | |
| key = element.GetChildAtIndex(0) | |
| return self.valobj.CreateValueFromData( | |
| "[%s]" % index, key.GetData(), key.GetType() | |
| ) | |
| def update(self): | |
| table = self.table() | |
| inner_table = table.GetChildMemberWithName("table") | |
| capacity = ( | |
| inner_table.GetChildMemberWithName("bucket_mask").GetValueAsUnsigned() + 1 | |
| ) | |
| ctrl = inner_table.GetChildMemberWithName("ctrl").GetChildAtIndex(0) | |
| self.size = inner_table.GetChildMemberWithName("items").GetValueAsUnsigned() | |
| self.pair_type = table.GetType().GetTemplateArgumentType(0) | |
| if not self.pair_type.IsValid(): | |
| arg_name = next(get_template_args(table.GetTypeName())) | |
| self.pair_type = resolve_msvc_template_arg(arg_name, self.valobj.target) | |
| if self.pair_type.IsTypedefType(): | |
| self.pair_type = self.pair_type.GetTypedefedType() | |
| self.pair_type_size = self.pair_type.GetByteSize() | |
| self.new_layout = not inner_table.GetChildMemberWithName("data").IsValid() | |
| if self.new_layout: | |
| self.data_ptr = ctrl.Cast(self.pair_type.GetPointerType()) | |
| else: | |
| self.data_ptr = inner_table.GetChildMemberWithName("data").GetChildAtIndex( | |
| 0 | |
| ) | |
| u8_type = self.valobj.GetTarget().GetBasicType(eBasicTypeUnsignedChar) | |
| u8_type_size = ( | |
| self.valobj.GetTarget().GetBasicType(eBasicTypeUnsignedChar).GetByteSize() | |
| ) | |
| self.valid_indices = [] | |
| for idx in range(capacity): | |
| address = ctrl.GetValueAsUnsigned() + idx * u8_type_size | |
| value = ctrl.CreateValueFromAddress( | |
| "ctrl[%s]" % idx, address, u8_type | |
| ).GetValueAsUnsigned() | |
| is_present = value & 128 == 0 | |
| if is_present: | |
| self.valid_indices.append(idx) | |
| def table(self) -> SBValue: | |
| if self.show_values: | |
| hashbrown_hashmap = self.valobj.GetChildMemberWithName("base") | |
| else: | |
| # BACKCOMPAT: rust 1.47 | |
| # HashSet wraps either std HashMap or hashbrown::HashSet, which both | |
| # wrap hashbrown::HashMap, so either way we "unwrap" twice. | |
| hashbrown_hashmap = self.valobj.GetChildAtIndex(0).GetChildAtIndex(0) | |
| return hashbrown_hashmap.GetChildMemberWithName("table") | |
| def has_children(self) -> bool: | |
| return True | |
| def StdRcSummaryProvider(valobj: SBValue, _dict: LLDBOpaque) -> str: | |
| strong = valobj.GetChildMemberWithName("strong").GetValueAsUnsigned() | |
| weak = valobj.GetChildMemberWithName("weak").GetValueAsUnsigned() | |
| return "strong={}, weak={}".format(strong, weak) | |
| class StdRcSyntheticProvider: | |
| """Pretty-printer for alloc::rc::Rc<T> and alloc::sync::Arc<T> | |
| struct Rc<T> { ptr: NonNull<RcInner<T>>, ... } | |
| rust 1.31.1: struct NonNull<T> { pointer: NonZero<*const T> } | |
| rust 1.33.0: struct NonNull<T> { pointer: *const T } | |
| struct NonZero<T>(T) | |
| struct RcInner<T> { strong: Cell<usize>, weak: Cell<usize>, value: T } | |
| struct Arc<T> { ptr: NonNull<ArcInner<T>>, ... } | |
| struct ArcInner<T> { strong: atomic::Atomic<usize>, weak: atomic::Atomic<usize>, data: T } | |
| """ | |
| def __init__(self, valobj: SBValue, _dict: LLDBOpaque, is_atomic: bool = False): | |
| self.valobj = valobj | |
| self.ptr = unwrap_unique_or_non_null(self.valobj.GetChildMemberWithName("ptr")) | |
| self.value = self.ptr.GetChildMemberWithName("data" if is_atomic else "value") | |
| self.strong = unwrap_scalar_wrappers(self.ptr.GetChildMemberWithName("strong")) | |
| self.weak = unwrap_scalar_wrappers(self.ptr.GetChildMemberWithName("weak")) | |
| self.value_builder = ValueBuilder(valobj) | |
| self.update() | |
| def num_children(self) -> int: | |
| # Actually there are 3 children, but only the `value` should be shown as a child | |
| return 1 | |
| def get_child_index(self, name: str) -> int: | |
| if name == "value": | |
| return 0 | |
| if name == "strong": | |
| return 1 | |
| if name == "weak": | |
| return 2 | |
| return -1 | |
| def get_child_at_index(self, index: int) -> Optional[SBValue]: | |
| if index == 0: | |
| return self.value | |
| if index == 1: | |
| return self.value_builder.from_uint("strong", self.strong_count) | |
| if index == 2: | |
| return self.value_builder.from_uint("weak", self.weak_count) | |
| return None | |
| def update(self): | |
| self.strong_count = self.strong.GetValueAsUnsigned() | |
| self.weak_count = self.weak.GetValueAsUnsigned() - 1 | |
| def has_children(self) -> bool: | |
| return True | |
| class StdCellSyntheticProvider: | |
| """Pretty-printer for std::cell::Cell""" | |
| def __init__(self, valobj: SBValue, _dict: LLDBOpaque): | |
| self.valobj = valobj | |
| self.value = valobj.GetChildMemberWithName("value").GetChildAtIndex(0) | |
| def num_children(self) -> int: | |
| return 1 | |
| def get_child_index(self, name: str) -> int: | |
| if name == "value": | |
| return 0 | |
| return -1 | |
| def get_child_at_index(self, index: int) -> Optional[SBValue]: | |
| if index == 0: | |
| return self.value | |
| return None | |
| def update(self): | |
| pass | |
| def has_children(self) -> bool: | |
| return True | |
| def StdRefSummaryProvider(valobj: SBValue, _dict: LLDBOpaque) -> str: | |
| borrow = valobj.GetChildMemberWithName("borrow").GetValueAsSigned() | |
| return ( | |
| "borrow={}".format(borrow) if borrow >= 0 else "borrow_mut={}".format(-borrow) | |
| ) | |
| class StdRefSyntheticProvider: | |
| """Pretty-printer for std::cell::Ref, std::cell::RefMut, and std::cell::RefCell""" | |
| def __init__(self, valobj: SBValue, _dict: LLDBOpaque, is_cell: bool = False): | |
| self.valobj = valobj | |
| borrow = valobj.GetChildMemberWithName("borrow") | |
| value = valobj.GetChildMemberWithName("value") | |
| if is_cell: | |
| self.borrow = borrow.GetChildMemberWithName("value").GetChildMemberWithName( | |
| "value" | |
| ) | |
| self.value = value.GetChildMemberWithName("value") | |
| else: | |
| self.borrow = ( | |
| borrow.GetChildMemberWithName("borrow") | |
| .GetChildMemberWithName("value") | |
| .GetChildMemberWithName("value") | |
| ) | |
| self.value = value.Dereference() | |
| self.value_builder = ValueBuilder(valobj) | |
| self.update() | |
| def num_children(self) -> int: | |
| # Actually there are 2 children, but only the `value` should be shown as a child | |
| return 1 | |
| def get_child_index(self, name: str) -> int: | |
| if name == "value": | |
| return 0 | |
| if name == "borrow": | |
| return 1 | |
| return -1 | |
| def get_child_at_index(self, index: int) -> Optional[SBValue]: | |
| if index == 0: | |
| return self.value | |
| if index == 1: | |
| return self.value_builder.from_int("borrow", self.borrow_count) | |
| return None | |
| def update(self): | |
| self.borrow_count = self.borrow.GetValueAsSigned() | |
| def has_children(self) -> bool: | |
| return True | |
| def StdNonZeroNumberSummaryProvider(valobj: SBValue, _dict: LLDBOpaque) -> str: | |
| inner = valobj.GetChildAtIndex(0) | |
| inner_inner = inner.GetChildAtIndex(0) | |
| # FIXME: Avoid printing as character literal, | |
| # see https://github.com/llvm/llvm-project/issues/65076. | |
| if inner_inner.GetTypeName() in ["char", "unsigned char"]: | |
| return str(inner_inner.GetValueAsSigned()) | |
| else: | |
| return inner_inner.GetValue() |