From 4de7c3cfd4a31a157f5d37c10d9048bd7cc92340 Mon Sep 17 00:00:00 2001 From: Walnut Date: Mon, 26 Oct 2026 04:37:09 -0500 Subject: [PATCH 1/2] add wide pointer lldb visualizer --- src/etc/lldb_lookup.py | 56 +++++++++++++ src/etc/lldb_providers.py | 168 +++++++++++++++++++++++++++++++++++++- 2 files changed, 223 insertions(+), 1 deletion(-) diff --git a/src/etc/lldb_lookup.py b/src/etc/lldb_lookup.py index 957b8bb557f34..763efc898846e 100644 --- a/src/etc/lldb_lookup.py +++ b/src/etc/lldb_lookup.py @@ -39,6 +39,8 @@ StructSyntheticProvider, TupleSummaryProvider, TupleSyntheticProvider, + UnsizedSyntheticProvider, + WidePtrSyntheticProvider, f16SummaryProvider, f128SummaryProvider, ) @@ -194,6 +196,26 @@ def register_providers_compatibility(): DEFAULT_TYPE_OPTIONS | lldb.eTypeOptionHideChildren, ) + # Catches all types of the form `T[]` and `[T]` + # MUST be registered before slice and &str visualizers so that those specialized ones can + # override this one. + register_synth( + UnsizedSyntheticProvider, + lldb.SBTypeNameSpecifier( + MOD_PREFIX + is_empty_array_type.__name__, lldb.eFormatterMatchCallback + ), + ) + + # Handles generic wide pointers. + # MUST be registered before slice and &str visualizers so that those specialized ones can + # override this one. + register_synth( + WidePtrSyntheticProvider, + lldb.SBTypeNameSpecifier( + MOD_PREFIX + is_wide_ptr_type.__name__, lldb.eFormatterMatchCallback + ), + ) + # Tuple-structs register_synth( TupleSyntheticProvider, @@ -519,6 +541,40 @@ def is_msvc_f128(type: lldb.SBType, _dict: LLDBOpaque) -> bool: return type.GetName() == "f128" and type.IsAggregateType() +def is_wide_ptr_type(type: lldb.SBType, _dict: LLDBOpaque) -> bool: + if type.GetNumberOfFields() == 2: + data_ptr = None + length = None + + for f in type.fields: + if f.GetName() == "data_ptr": + data_ptr = f + elif f.GetName() == "length": + length = f + + return ( + length is not None + and data_ptr is not None + and data_ptr.GetType().IsPointerType() + ) + + return False + + +def is_empty_array_type(type: lldb.SBType, _dict: LLDBOpaque) -> bool: + # there's not a great way to check if an array is 0-sized. `GetByteSize` returns the + # same size as an array of length 1. The length isn't considered a template arg. + # `GetNumberOfFields` returns 0 regardless of the number of elements. + # While we could just check if the type name ends in `[]` (normalized) or starts + # with '[' and ends with ']' (non-normalized), it's a bit safer and more general + # purpose to compare it with the expected type. + if not type.IsArrayType(): + return False + expected = type.GetArrayElementType().GetArrayType(0) + + return type.GetName() == expected.GetName() + + def classify_rust_type(type: lldb.SBType, is_msvc: bool) -> RustType: if type.IsPointerType(): return RustType.Indirection diff --git a/src/etc/lldb_providers.py b/src/etc/lldb_providers.py index 4e0452d4cf77b..46ba7a16f0578 100644 --- a/src/etc/lldb_providers.py +++ b/src/etc/lldb_providers.py @@ -3,10 +3,12 @@ import sys from enum import Flag, auto from typing import TYPE_CHECKING, ClassVar, Dict, Generator, List, Optional +from weakref import WeakValueDictionary from lldb import ( SBData, SBError, + SBValue, eBasicTypeChar32, eBasicTypeDouble, eBasicTypeFloat, @@ -24,7 +26,7 @@ from rust_types import is_tuple_fields if TYPE_CHECKING: - from lldb import SBProcess, SBTarget, SBType, SBTypeStaticField, SBValue + from lldb import SBProcess, SBTarget, SBType, SBTypeStaticField # from lldb.formatters import Logger @@ -1733,3 +1735,167 @@ def StdNonZeroNumberSummaryProvider(valobj: SBValue, _dict: LLDBOpaque) -> str: return str(inner_inner.GetValueAsSigned()) else: return inner_inner.GetValue() + + +class WidePtrSyntheticProvider: + """ + Visualizer for pointers to dynamically sized types. This DOES NOT cover slices (`&[T]`) or + `&str`. Those have fast-path providers. This is specifically meant to handle references/pointers + to structs with unsized tails, such as: + + ``` + struct T { + a: u8, + b: f64, + c: [i32], + } + ``` + """ + + WIDE_PTR_BY_UNSIZED_CHILD_ID: ClassVar[WeakValueDictionary[int, SBValue]] = ( + WeakValueDictionary() + ) + + __slots__ = ("data_ptr", "unsized_child_depth", "valobj") + + def __init__(self, valobj: SBValue, _dict: LLDBOpaque): + self.valobj = valobj + self.data_ptr = valobj.GetChildMemberWithName("data_ptr") + self.unsized_child_depth = self._find_unsized_child() + + def _find_unsized_child(self) -> Optional[int]: + """Returns the depth of the unsized child. This is the number of times one must execute: + + ``` + valobj = valobj.GetChildAtIndex(valobj.GetNumChildren() - 1).GetNonSyntheticValue() + ``` + + to retrieve the unsized child + + """ + ty = self.data_ptr.Dereference().GetType() + depth = 0 + + while ty.IsValid() and depth < 1000: + depth += 1 + ty = ty.GetFieldAtIndex(max(0, ty.GetNumberOfFields() - 1)).GetType() + + if ty.IsArrayType(): + # ensure that we actually have a 0-length array as the final field + expected = ty.GetArrayElementType().GetArrayType(0) + if expected.GetName() == ty.GetName(): + return depth + + return None + + def _get_unsized_child(self) -> SBValue: + if self.unsized_child_depth is None: + return SBValue() + + valobj = self.data_ptr.GetNonSyntheticValue() + + for _ in range(self.unsized_child_depth): + valobj = valobj.GetChildAtIndex( + max(0, valobj.GetNumChildren() - 1) + ).GetNonSyntheticValue() + + return valobj + + def update(self): + # If the `UnsizedSyntheticProvider` can't find this by itself, we need to cache this class + # manually so that `UnsizedSyntheticProvider` can still look it up. + if LLDBFeature.GetParent not in FEATURE_FLAGS: + unsized_child = self._get_unsized_child() + if unsized_child.IsValid(): + print(f"found child (id#{unsized_child.GetID()}): {unsized_child}") + WidePtrSyntheticProvider.WIDE_PTR_BY_UNSIZED_CHILD_ID[ + unsized_child.GetID() + ] = self.valobj + + def has_children(self) -> bool: + return True + + def num_children(self) -> int: + return self.data_ptr.GetNumChildren() + + def get_child_idx(self, name: str) -> int: + return self.data_ptr.GetIndexOfChildWithName(name) + + def get_child_at_index(self, idx: int) -> Optional[SBValue]: + return self.data_ptr.GetChildAtIndex(idx) + + +class UnsizedSyntheticProvider: + valobj: SBValue + parent: SBValue + as_array: SBValue + len: int + __slots__ = ("as_array", "len", "parent", "type_name", "valobj") + + def __init__(self, valobj: SBValue, _dict: LLDBOpaque): + self.valobj = valobj + self.as_array = valobj + self.parent = SBValue() + self.len = 0 + + self.type_name = f"[{valobj.GetType().GetArrayElementType().GetName()}]" + + # `GetParent` allows us to traverse up the valobj tree until we find the pointer, which we + # can grab the length from. Without the ability to do that, we need to look up the parent + # from the cache + if LLDBFeature.GetParent in FEATURE_FLAGS: + self._find_parent_wideptr() + else: + self.parent = WidePtrSyntheticProvider.WIDE_PTR_BY_UNSIZED_CHILD_ID.get( + self.valobj.GetID(), self.parent + ) + + def _find_parent_wideptr(self): + valobj = self.valobj + # just in case + no_inf_loop = 0 + + while valobj.IsValid() and no_inf_loop < 1000: + no_inf_loop += 1 + valobj = valobj.GetParent().GetNonSyntheticValue() + + if ( + valobj.GetNumChildren() == 2 + and valobj.GetChildMemberWithName("data_ptr").IsValid() + and (len := valobj.GetChildMemberWithName("length")).IsValid() + ): + self.parent = valobj + self.len = len.GetValueAsUnsigned() + + def update(self): + if self.parent.IsValid(): + self.len = self.parent.GetChildMemberWithName("length").GetValueAsUnsigned( + 0 + ) + + self.as_array = self.valobj.Cast( + self.valobj.GetType().GetArrayElementType().GetArrayType(self.len) + ) + else: + self.as_array = self.valobj + self.len = 0 + + def has_children(self) -> bool: + return self.len != 0 + + def num_children(self): + return self.len + + 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]: + if index < self.len: + return self.as_array.GetChildAtIndex(index) + + def get_type_name(self) -> str: + return self.type_name From c38cf3787e4178d54c36e365384a462fc2bf6185 Mon Sep 17 00:00:00 2001 From: Walnut Date: Mon, 26 Oct 2026 04:37:29 -0500 Subject: [PATCH 2/2] add wide pointer visualizer test --- .../lldb_input/non_windows.json | 427 ++++++++++++++++++ .../lldb_input/windows_gnu.json | 427 ++++++++++++++++++ tests/debuginfo/wideptr_visualizer/main.rs | 132 ++++++ 3 files changed, 986 insertions(+) create mode 100644 tests/debuginfo/wideptr_visualizer/lldb_input/non_windows.json create mode 100644 tests/debuginfo/wideptr_visualizer/lldb_input/windows_gnu.json create mode 100644 tests/debuginfo/wideptr_visualizer/main.rs diff --git a/tests/debuginfo/wideptr_visualizer/lldb_input/non_windows.json b/tests/debuginfo/wideptr_visualizer/lldb_input/non_windows.json new file mode 100644 index 0000000000000..c5db2e5b097a3 --- /dev/null +++ b/tests/debuginfo/wideptr_visualizer/lldb_input/non_windows.json @@ -0,0 +1,427 @@ +{ + "bless_metadata": { + "python_version": "3.14.8 (main, Oct 1 2026, 00:00:00) [GCC 16.2.1 20260819 (Red Hat 16.2.1-2)]", + "debugger_version": "lldb version 22.1.8", + "feature_flags": "LLDBFeature.StaticFields|TypeRecognizers|Float128" + }, + "breakpoints": [ + { + "a": { + "type": "*mut main::A", + "pretty_print": "{x:1, y:2, data:({f1:3, f2:4, f3:5}, {f1:6, f2:7, f3:8}, {f1:9, f2:10, f3:11})}", + "synthetic": "lldb_lookup.WidePtrSyntheticProvider", + "summary": "lldb_lookup.StructSummaryProvider", + "children": [ + { + "name": "x", + "type": "unsigned long", + "value": 1 + }, + { + "name": "y", + "type": "unsigned short", + "value": 2 + }, + { + "name": "data", + "type": "main::Sized[]", + "children": { + "type": "main::Sized", + "vals": [ + { + "children": [ + { + "name": "f1", + "type": "unsigned short", + "value": 3 + }, + { + "name": "f2", + "type": "unsigned char", + "value": 4 + }, + { + "name": "f3", + "type": "unsigned char", + "value": 5 + } + ] + }, + { + "children": [ + { + "name": "f1", + "type": "unsigned short", + "value": 6 + }, + { + "name": "f2", + "type": "unsigned char", + "value": 7 + }, + { + "name": "f3", + "type": "unsigned char", + "value": 8 + } + ] + }, + { + "children": [ + { + "name": "f1", + "type": "unsigned short", + "value": 9 + }, + { + "name": "f2", + "type": "unsigned char", + "value": 10 + }, + { + "name": "f3", + "type": "unsigned char", + "value": 11 + } + ] + } + ] + } + } + ] + }, + "b": { + "type": "*mut main::B", + "pretty_print": "{x:1, y:2, data:{f1:3, f2:4, f3:(5, 6, 7, 8, 9, 10, 11, 12, 13)}}", + "synthetic": "lldb_lookup.WidePtrSyntheticProvider", + "summary": "lldb_lookup.StructSummaryProvider", + "children": [ + { + "name": "x", + "type": "unsigned long", + "value": 1 + }, + { + "name": "y", + "type": "unsigned short", + "value": 2 + }, + { + "name": "data", + "type": "main::Unsized", + "children": [ + { + "name": "f1", + "type": "unsigned short", + "value": 3 + }, + { + "name": "f2", + "type": "unsigned char", + "value": 4 + }, + { + "name": "f3", + "type": "unsigned char[]", + "children": { + "type": "unsigned char", + "vals": [ + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13 + ] + } + } + ] + } + ] + }, + "a_ref": { + "type": "&mut main::A", + "pretty_print": "{x:1, y:2, data:({f1:3, f2:4, f3:5}, {f1:6, f2:7, f3:8}, {f1:9, f2:10, f3:11})}", + "synthetic": "lldb_lookup.WidePtrSyntheticProvider", + "summary": "lldb_lookup.StructSummaryProvider", + "children": [ + { + "name": "x", + "type": "unsigned long", + "value": 1 + }, + { + "name": "y", + "type": "unsigned short", + "value": 2 + }, + { + "name": "data", + "type": "main::Sized[]", + "children": { + "type": "main::Sized", + "vals": [ + { + "children": [ + { + "name": "f1", + "type": "unsigned short", + "value": 3 + }, + { + "name": "f2", + "type": "unsigned char", + "value": 4 + }, + { + "name": "f3", + "type": "unsigned char", + "value": 5 + } + ] + }, + { + "children": [ + { + "name": "f1", + "type": "unsigned short", + "value": 6 + }, + { + "name": "f2", + "type": "unsigned char", + "value": 7 + }, + { + "name": "f3", + "type": "unsigned char", + "value": 8 + } + ] + }, + { + "children": [ + { + "name": "f1", + "type": "unsigned short", + "value": 9 + }, + { + "name": "f2", + "type": "unsigned char", + "value": 10 + }, + { + "name": "f3", + "type": "unsigned char", + "value": 11 + } + ] + } + ] + } + } + ] + }, + "b_ref": { + "type": "&mut main::B", + "pretty_print": "{x:1, y:2, data:{f1:3, f2:4, f3:(5, 6, 7, 8, 9, 10, 11, 12, 13)}}", + "synthetic": "lldb_lookup.WidePtrSyntheticProvider", + "summary": "lldb_lookup.StructSummaryProvider", + "children": [ + { + "name": "x", + "type": "unsigned long", + "value": 1 + }, + { + "name": "y", + "type": "unsigned short", + "value": 2 + }, + { + "name": "data", + "type": "main::Unsized", + "children": [ + { + "name": "f1", + "type": "unsigned short", + "value": 3 + }, + { + "name": "f2", + "type": "unsigned char", + "value": 4 + }, + { + "name": "f3", + "type": "unsigned char[]", + "children": { + "type": "unsigned char", + "vals": [ + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13 + ] + } + } + ] + } + ] + } + } + ], + "types": { + "&mut main::A": { + "size": 16, + "type_class": 16384, + "fields": [ + { + "name": "data_ptr", + "type": "main::A *", + "offset": 0 + }, + { + "name": "length", + "type": "usize", + "offset": 8 + } + ] + }, + "&mut main::B": { + "size": 16, + "type_class": 16384, + "fields": [ + { + "name": "data_ptr", + "type": "main::B *", + "offset": 0 + }, + { + "name": "length", + "type": "usize", + "offset": 8 + } + ] + }, + "*mut main::A": { + "size": 16, + "type_class": 16384, + "fields": [ + { + "name": "data_ptr", + "type": "main::A *", + "offset": 0 + }, + { + "name": "length", + "type": "usize", + "offset": 8 + } + ] + }, + "*mut main::B": { + "size": 16, + "type_class": 16384, + "fields": [ + { + "name": "data_ptr", + "type": "main::B *", + "offset": 0 + }, + { + "name": "length", + "type": "usize", + "offset": 8 + } + ] + }, + "main::A *": { + "size": 8, + "type_class": 4096 + }, + "main::B *": { + "size": 8, + "type_class": 4096 + }, + "main::Sized": { + "size": 4, + "type_class": 16384, + "fields": [ + { + "name": "f1", + "type": "unsigned short", + "offset": 0 + }, + { + "name": "f2", + "type": "unsigned char", + "offset": 2 + }, + { + "name": "f3", + "type": "unsigned char", + "offset": 3 + } + ] + }, + "main::Sized[]": { + "size": 4, + "type_class": 1 + }, + "main::Unsized": { + "size": 4, + "type_class": 16384, + "fields": [ + { + "name": "f1", + "type": "unsigned short", + "offset": 0 + }, + { + "name": "f2", + "type": "unsigned char", + "offset": 2 + }, + { + "name": "f3", + "type": "unsigned char[]", + "offset": 3 + } + ] + }, + "unsigned char": { + "size": 1, + "type_class": 4, + "basic_type": 4 + }, + "unsigned char[]": { + "size": 1, + "type_class": 1 + }, + "unsigned long": { + "size": 8, + "type_class": 4, + "basic_type": 16 + }, + "unsigned short": { + "size": 2, + "type_class": 4, + "basic_type": 12 + }, + "usize": { + "size": 8, + "type_class": 32768, + "basic_type": 16 + } + } +} diff --git a/tests/debuginfo/wideptr_visualizer/lldb_input/windows_gnu.json b/tests/debuginfo/wideptr_visualizer/lldb_input/windows_gnu.json new file mode 100644 index 0000000000000..b9a8b652ee76e --- /dev/null +++ b/tests/debuginfo/wideptr_visualizer/lldb_input/windows_gnu.json @@ -0,0 +1,427 @@ +{ + "bless_metadata": { + "python_version": "3.11.9 (tags/v3.11.9:de54cf5, Apr 2 2024, 10:12:12) [MSC v.1938 64 bit (AMD64)]", + "debugger_version": "lldb version 23.1.3 (https://github.com/llvm/llvm-project revision 0d261d1ca552c95a8f007e061c787ac7132fbcbc)\n clang revision 0d261d1ca552c95a8f007e061c787ac7132fbcbc\n llvm revision 0d261d1ca552c95a8f007e061c787ac7132fbcbc", + "feature_flags": "LLDBFeature.StaticFields|TypeRecognizers|Float128|GetParent|ProviderDecorator" + }, + "breakpoints": [ + { + "a": { + "type": "*mut main::A", + "pretty_print": "{x:1, y:2, data:({f1:3, f2:4, f3:5}, {f1:6, f2:7, f3:8}, {f1:9, f2:10, f3:11})}", + "synthetic": "lldb_lookup.WidePtrSyntheticProvider", + "summary": "lldb_lookup.StructSummaryProvider", + "children": [ + { + "name": "x", + "type": "unsigned long long", + "value": 1 + }, + { + "name": "y", + "type": "unsigned short", + "value": 2 + }, + { + "name": "data", + "type": "main::Sized[]", + "children": { + "type": "main::Sized", + "vals": [ + { + "children": [ + { + "name": "f1", + "type": "unsigned short", + "value": 3 + }, + { + "name": "f2", + "type": "unsigned char", + "value": 4 + }, + { + "name": "f3", + "type": "unsigned char", + "value": 5 + } + ] + }, + { + "children": [ + { + "name": "f1", + "type": "unsigned short", + "value": 6 + }, + { + "name": "f2", + "type": "unsigned char", + "value": 7 + }, + { + "name": "f3", + "type": "unsigned char", + "value": 8 + } + ] + }, + { + "children": [ + { + "name": "f1", + "type": "unsigned short", + "value": 9 + }, + { + "name": "f2", + "type": "unsigned char", + "value": 10 + }, + { + "name": "f3", + "type": "unsigned char", + "value": 11 + } + ] + } + ] + } + } + ] + }, + "b": { + "type": "*mut main::B", + "pretty_print": "{x:1, y:2, data:{f1:3, f2:4, f3:(5, 6, 7, 8, 9, 10, 11, 12, 13)}}", + "synthetic": "lldb_lookup.WidePtrSyntheticProvider", + "summary": "lldb_lookup.StructSummaryProvider", + "children": [ + { + "name": "x", + "type": "unsigned long long", + "value": 1 + }, + { + "name": "y", + "type": "unsigned short", + "value": 2 + }, + { + "name": "data", + "type": "main::Unsized", + "children": [ + { + "name": "f1", + "type": "unsigned short", + "value": 3 + }, + { + "name": "f2", + "type": "unsigned char", + "value": 4 + }, + { + "name": "f3", + "type": "unsigned char[]", + "children": { + "type": "unsigned char", + "vals": [ + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13 + ] + } + } + ] + } + ] + }, + "a_ref": { + "type": "&mut main::A", + "pretty_print": "{x:1, y:2, data:({f1:3, f2:4, f3:5}, {f1:6, f2:7, f3:8}, {f1:9, f2:10, f3:11})}", + "synthetic": "lldb_lookup.WidePtrSyntheticProvider", + "summary": "lldb_lookup.StructSummaryProvider", + "children": [ + { + "name": "x", + "type": "unsigned long long", + "value": 1 + }, + { + "name": "y", + "type": "unsigned short", + "value": 2 + }, + { + "name": "data", + "type": "main::Sized[]", + "children": { + "type": "main::Sized", + "vals": [ + { + "children": [ + { + "name": "f1", + "type": "unsigned short", + "value": 3 + }, + { + "name": "f2", + "type": "unsigned char", + "value": 4 + }, + { + "name": "f3", + "type": "unsigned char", + "value": 5 + } + ] + }, + { + "children": [ + { + "name": "f1", + "type": "unsigned short", + "value": 6 + }, + { + "name": "f2", + "type": "unsigned char", + "value": 7 + }, + { + "name": "f3", + "type": "unsigned char", + "value": 8 + } + ] + }, + { + "children": [ + { + "name": "f1", + "type": "unsigned short", + "value": 9 + }, + { + "name": "f2", + "type": "unsigned char", + "value": 10 + }, + { + "name": "f3", + "type": "unsigned char", + "value": 11 + } + ] + } + ] + } + } + ] + }, + "b_ref": { + "type": "&mut main::B", + "pretty_print": "{x:1, y:2, data:{f1:3, f2:4, f3:(5, 6, 7, 8, 9, 10, 11, 12, 13)}}", + "synthetic": "lldb_lookup.WidePtrSyntheticProvider", + "summary": "lldb_lookup.StructSummaryProvider", + "children": [ + { + "name": "x", + "type": "unsigned long long", + "value": 1 + }, + { + "name": "y", + "type": "unsigned short", + "value": 2 + }, + { + "name": "data", + "type": "main::Unsized", + "children": [ + { + "name": "f1", + "type": "unsigned short", + "value": 3 + }, + { + "name": "f2", + "type": "unsigned char", + "value": 4 + }, + { + "name": "f3", + "type": "unsigned char[]", + "children": { + "type": "unsigned char", + "vals": [ + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13 + ] + } + } + ] + } + ] + } + } + ], + "types": { + "&mut main::A": { + "size": 16, + "type_class": 16384, + "fields": [ + { + "name": "data_ptr", + "type": "main::A *", + "offset": 0 + }, + { + "name": "length", + "type": "usize", + "offset": 8 + } + ] + }, + "&mut main::B": { + "size": 16, + "type_class": 16384, + "fields": [ + { + "name": "data_ptr", + "type": "main::B *", + "offset": 0 + }, + { + "name": "length", + "type": "usize", + "offset": 8 + } + ] + }, + "*mut main::A": { + "size": 16, + "type_class": 16384, + "fields": [ + { + "name": "data_ptr", + "type": "main::A *", + "offset": 0 + }, + { + "name": "length", + "type": "usize", + "offset": 8 + } + ] + }, + "*mut main::B": { + "size": 16, + "type_class": 16384, + "fields": [ + { + "name": "data_ptr", + "type": "main::B *", + "offset": 0 + }, + { + "name": "length", + "type": "usize", + "offset": 8 + } + ] + }, + "main::A *": { + "size": 8, + "type_class": 4096 + }, + "main::B *": { + "size": 8, + "type_class": 4096 + }, + "main::Sized": { + "size": 4, + "type_class": 16384, + "fields": [ + { + "name": "f1", + "type": "unsigned short", + "offset": 0 + }, + { + "name": "f2", + "type": "unsigned char", + "offset": 2 + }, + { + "name": "f3", + "type": "unsigned char", + "offset": 3 + } + ] + }, + "main::Sized[]": { + "size": 4, + "type_class": 1 + }, + "main::Unsized": { + "size": 4, + "type_class": 16384, + "fields": [ + { + "name": "f1", + "type": "unsigned short", + "offset": 0 + }, + { + "name": "f2", + "type": "unsigned char", + "offset": 2 + }, + { + "name": "f3", + "type": "unsigned char[]", + "offset": 3 + } + ] + }, + "unsigned char": { + "size": 1, + "type_class": 4, + "basic_type": 4 + }, + "unsigned char[]": { + "size": 1, + "type_class": 1 + }, + "unsigned long long": { + "size": 8, + "type_class": 4, + "basic_type": 18 + }, + "unsigned short": { + "size": 2, + "type_class": 4, + "basic_type": 12 + }, + "usize": { + "size": 8, + "type_class": 32768, + "basic_type": 18 + } + } +} diff --git a/tests/debuginfo/wideptr_visualizer/main.rs b/tests/debuginfo/wideptr_visualizer/main.rs new file mode 100644 index 0000000000000..4223b081da0d4 --- /dev/null +++ b/tests/debuginfo/wideptr_visualizer/main.rs @@ -0,0 +1,132 @@ +// Ensures wide pointers to dynamically sized types are visualized properly. Note that this does +// not include slice/str wide pointers, only wide pointers that point to dynamically sized +// user defined types. + +//@ compile-flags:-g +//@ min-llvm-lldb-version: 21.1.0 +//@ min-apple-lldb-version: 1703.0.236.21 + +// LLDB completely omits ZST-typed fields from the parsed *type*: +// ``` +// ignore-tidy-linelength +// (lldb) script lldb.frame.var("a").GetNonSyntheticValue().GetChildMemberWithName("data_ptr").Dereference().GetType() +// struct A { +// unsigned long long x; +// unsigned short y; +// } +// ``` +// +// Because of this, we can't even attempt to make this visualizer work on MSVC because we have no +// way to know that there's an unsized tail *at all*, let alone what type it's supposed to be. + +//@ ignore-msvc + +//@ ignore-backends: gcc + + +// === LLDB TESTS ================================================================================== + +//@ lldb-command:run + +// prevents `unsigned char[]` for printing as a string. +//@ lldb-command:type category disable system + +//@ lldb-repr:a +//@ lldb-repr:b +//@ lldb-repr:a_ref +//@ lldb-repr:b_ref + + +#![allow(unused)] +use std::{ + alloc::{Layout, alloc_zeroed}, + ptr, +}; + +// `Sized` and `Unsized` as well as `A` and `B` are laid out in such a way that if we stop +// outputting the correct debug info for `[T]`, they will be impossible to tell apart from their +// layouts in the DWARF/PDB data. Not only are the types all the same size, the unsized portions +// also "hide" within the natural padding of the parent struct + +#[derive(Clone, Copy)] +#[repr(C)] +struct Sized { + f1: u16, + f2: u8, + f3: u8, +} + +#[repr(C)] +struct Unsized { + f1: u16, + f2: u8, + f3: [u8], +} + +#[repr(C)] +struct A { + x: u64, + y: u16, + data: [Sized], +} + +#[repr(C)] +struct B { + x: u64, + y: u16, + data: Unsized, +} + +fn main() -> Result<(), std::alloc::LayoutError> { + unsafe { + // 10 bytes + let base_l = Layout::new::().extend(Layout::new::())?.0; + + // 10 + (4 * 3) = 22 bytes + let a_l = base_l.extend(Layout::array::(3)?)?.0; + + // 10 + 3 + (1 * 9) = 22 bytes + let b_l = base_l + .extend(Layout::new::())? + .0 + .extend(Layout::new::())? + .0 + .extend(Layout::array::(9)?)? + .0; + + let a = ptr::slice_from_raw_parts(alloc_zeroed(a_l), 3) as *mut A; + let b = ptr::slice_from_raw_parts_mut(alloc_zeroed(b_l), 9) as *mut B; + + let a_ref = a.as_mut().unwrap(); + + a_ref.x = 1; + a_ref.y = 2; + a_ref.data.copy_from_slice(&[ + Sized { + f1: 3, + f2: 4, + f3: 5, + }, + Sized { + f1: 6, + f2: 7, + f3: 8, + }, + Sized { + f1: 9, + f2: 10, + f3: 11, + }, + ]); + + let b_ref = b.as_mut().unwrap(); + + b_ref.x = 1; + b_ref.y = 2; + b_ref.data.f1 = 3; + b_ref.data.f2 = 4; + b_ref.data.f3.copy_from_slice(&[5, 6, 7, 8, 9, 10, 11, 12, 13]); + + Ok(()) // #break + } +}