Skip to content

Commit f41dc23

Browse files
authored
GDB pretty-printers (#8556)
GDB pretty-printers for ArrayRef, Array<T>, Id, and Arc<dyn T> like Arc<dyn ArrayPlugin> Signed-off-by: Mikhail Kot <mikhail@spiraldb.com>
1 parent 1118a20 commit f41dc23

1 file changed

Lines changed: 306 additions & 0 deletions

File tree

scripts/vortex_gdb.py

Lines changed: 306 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,306 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
# SPDX-FileCopyrightText: Copyright the Vortex contributors
3+
4+
"""
5+
GDB pretty-printers. Usage:
6+
7+
(gdb) source scripts/vortex_gdb.py
8+
...
9+
(gdb) print values_idx_offsets
10+
$1 = vortex.primitive(u32, len=465) @ 0x20005ab0d20 [strong=2, weak=0]
11+
(gdb) print values
12+
$2 = fastlanes.bitpacked(i16, len=75133) @ 0x20005ab09a0 [strong=2, weak=0]
13+
(gdb) print encoding_id
14+
$3 = Id("vortex.primitive")
15+
(gdb) print plugin
16+
$4 = dyn ArrayPlugin<vortex_array::arrays::primitive::vtable::Primitive> @ 0xaaaab6deb168
17+
(gdb) print plugin_arc
18+
$5 = Arc<dyn ArrayPlugin<vortex_array::arrays::primitive::vtable::Primitive> @ 0x20000ab0310> [strong=3, weak=0]
19+
"""
20+
21+
from __future__ import annotations
22+
23+
import re
24+
from collections.abc import Callable
25+
26+
import gdb
27+
28+
_ARRAY_PLUGIN_FQN = "vortex_array::array::plugin::ArrayPlugin"
29+
_DYN_ARRAY_PLUGIN = "dyn " + _ARRAY_PLUGIN_FQN
30+
_AS_STR_NAME = "vortex_session::registry::Id::as_str"
31+
_ARRAY_REF_FQN = "vortex_array::array::erased::ArrayRef"
32+
_ARRAY_TYPED_PREFIX = "vortex_array::array::typed::Array<"
33+
_ID_FQN = "vortex_session::registry::Id"
34+
_ARRAY_INNER_TY_NAME = "vortex_array::array::typed::ArrayInner<dyn vortex_array::array::DynArrayData>"
35+
# "data: T" lives after [strong, weak] inside ArcInner. both are AtomicUsize.
36+
_ARC_INNER_DATA_OFFSET = 16
37+
38+
# DType variant -> Display template. "{n}" is nullability suffix.
39+
_DTYPE_TEMPLATES = {
40+
"Null": "null",
41+
"Bool": "bool{n}",
42+
"Utf8": "utf8{n}",
43+
"Binary": "binary{n}",
44+
"Union": "union(){n}",
45+
"Variant": "variant{n}",
46+
"Struct": "{{...}}{n}",
47+
"List": "list(...){n}",
48+
"FixedSizeList": "fixed_size_list(...){n}",
49+
"Decimal": "decimal(...){n}",
50+
"Extension": "ext",
51+
}
52+
_DTYPE_VARIANT_RE = re.compile(r"(?:^|::)DType::(\w+)")
53+
_PTYPE_VARIANT_RE = re.compile(r"(?:^|::)PType::(\w+)")
54+
_NULLABILITY_RE = re.compile(r"(?:^|::)Nullability::(\w+)")
55+
56+
57+
def _usize_size() -> int:
58+
return gdb.lookup_type("usize").sizeof
59+
60+
61+
def _read_usize(addr: int) -> int:
62+
return int(gdb.Value(addr).cast(gdb.lookup_type("usize").pointer()).dereference())
63+
64+
65+
def _concrete_type(val: gdb.Value, deref_path: str) -> str | None:
66+
try:
67+
gdb.set_convenience_variable("vortex_tmp", val)
68+
return gdb.parse_and_eval(f"*$vortex_tmp{deref_path}").type.name
69+
except (gdb.error, AttributeError):
70+
return None
71+
72+
73+
def _trait_fat_pointer(val: gdb.Value) -> tuple[int, int] | None:
74+
# field names vary by rustc
75+
for p in ("pointer", "data_ptr", "__0"):
76+
for v in ("vtable", "__1"):
77+
try:
78+
return int(val[p]), int(val[v])
79+
except (gdb.error, RuntimeError, KeyError):
80+
continue
81+
try:
82+
fields = [f for f in val.type.fields() if f.name and not f.artificial]
83+
except (gdb.error, RuntimeError):
84+
return None
85+
if len(fields) == 2:
86+
try:
87+
return int(val[fields[0].name]), int(val[fields[1].name])
88+
except (gdb.error, RuntimeError):
89+
return None
90+
return None
91+
92+
93+
def _spur_from_id(val: gdb.Value) -> int | None:
94+
# Id(Spur { key: NonZero<u32>(T) }) -> T
95+
cur = val
96+
for _ in range(8):
97+
try:
98+
t = cur.type.strip_typedefs()
99+
except gdb.error:
100+
break
101+
if t.code != gdb.TYPE_CODE_STRUCT:
102+
break
103+
try:
104+
fields = [f for f in t.fields() if f.name and not f.artificial and not f.is_base_class]
105+
except (gdb.error, RuntimeError):
106+
break
107+
if len(fields) != 1:
108+
break
109+
cur = cur[fields[0].name]
110+
try:
111+
return int(cur)
112+
except (gdb.error, ValueError, TypeError):
113+
return None
114+
115+
116+
def _str_to_python(s: gdb.Value) -> str | None:
117+
for ptr_n, len_n in (("data_ptr", "length"), ("ptr", "len"), ("__0", "__1")):
118+
try:
119+
data_ptr, length = int(s[ptr_n]), int(s[len_n])
120+
break
121+
except (gdb.error, RuntimeError):
122+
continue
123+
else:
124+
return None
125+
if length == 0:
126+
return ""
127+
if data_ptr == 0 or length > 4096:
128+
return None
129+
try:
130+
return gdb.selected_inferior().read_memory(data_ptr, length).tobytes().decode("utf-8")
131+
except (gdb.error, UnicodeDecodeError):
132+
return None
133+
134+
135+
def _id_to_string(id_addr: int) -> str | None:
136+
sym = gdb.lookup_global_symbol(_AS_STR_NAME) or gdb.lookup_static_symbol(_AS_STR_NAME)
137+
if sym is None:
138+
return None
139+
try:
140+
id_ptr = gdb.Value(id_addr).cast(gdb.lookup_type(_ID_FQN).pointer())
141+
return _str_to_python(sym.value()(id_ptr))
142+
except gdb.error:
143+
return None
144+
145+
146+
def _format_dtype(dtype_val: gdb.Value) -> str:
147+
s = str(dtype_val).strip()
148+
m = _DTYPE_VARIANT_RE.search(s)
149+
if m is None:
150+
return s
151+
variant = m.group(1)
152+
nm = _NULLABILITY_RE.search(s)
153+
n = "?" if nm is not None and nm.group(1) == "Nullable" else ""
154+
if variant == "Primitive":
155+
pm = _PTYPE_VARIANT_RE.search(s)
156+
return f"{pm.group(1).lower() if pm else '?'}{n}"
157+
tmpl = _DTYPE_TEMPLATES.get(variant)
158+
return tmpl.format(n=n) if tmpl is not None else f"{variant}{n}"
159+
160+
161+
def _array_inner_at(addr: int) -> gdb.Value | None:
162+
try:
163+
return gdb.Value(addr).cast(gdb.lookup_type(_ARRAY_INNER_TY_NAME).pointer()).dereference()
164+
except gdb.error:
165+
return None
166+
167+
168+
def _format_id(val: gdb.Value) -> str:
169+
addr = val.address
170+
if addr is not None:
171+
text = _id_to_string(int(addr))
172+
if text is not None:
173+
return f'Id("{text}")'
174+
spur = _spur_from_id(val)
175+
return f"Id({spur})" if spur is not None else "Id(?)"
176+
177+
178+
def _format_dyn_plugin(val: gdb.Value) -> str:
179+
ty = _concrete_type(val, "") or "?"
180+
fp = _trait_fat_pointer(val)
181+
addr = f"{fp[0]:#x}" if fp else "?"
182+
return f"dyn ArrayPlugin<{ty}> @ {addr}"
183+
184+
185+
def _format_arc_dyn_plugin(val: gdb.Value) -> str:
186+
ty = _concrete_type(val, ".ptr.pointer") or "?"
187+
try:
188+
fp = _trait_fat_pointer(val["ptr"]["pointer"])
189+
except gdb.error:
190+
fp = None
191+
if fp is None:
192+
return f"Arc<dyn ArrayPlugin<{ty}>>"
193+
arc_inner_base, vtable_ptr = fp
194+
usize = _usize_size()
195+
try:
196+
strong = _read_usize(arc_inner_base)
197+
# ArcInner.weak = Arc::weak_count() + 1
198+
weak = _read_usize(arc_inner_base + usize) - 1
199+
align = _read_usize(vtable_ptr + 2 * usize) or usize
200+
except gdb.error:
201+
return f"Arc<dyn ArrayPlugin<{ty}>>"
202+
data_offset = (2 * usize + align - 1) & ~(align - 1)
203+
return f"Arc<dyn ArrayPlugin<{ty}> @ {arc_inner_base + data_offset:#x}> [strong={strong}, weak={weak}]"
204+
205+
206+
def _format_array_ref(val: gdb.Value) -> str:
207+
try:
208+
arc = val[val.type.fields()[0].name]
209+
fat_ptr = arc["ptr"]["pointer"]
210+
except (gdb.error, RuntimeError, IndexError):
211+
return "ArrayRef(?)"
212+
213+
fp = _trait_fat_pointer(fat_ptr) or (int(fat_ptr), None)
214+
arc_inner_base = fp[0]
215+
inner = _array_inner_at(arc_inner_base + _ARC_INNER_DATA_OFFSET)
216+
if inner is None:
217+
return f"ArrayRef(?) @ {arc_inner_base:#x}"
218+
219+
def _try(fn, default):
220+
try:
221+
return fn()
222+
except (gdb.error, RuntimeError):
223+
return default
224+
225+
len_ = _try(lambda: int(inner["len"]), None)
226+
enc = inner["encoding_id"]
227+
encoding = (_id_to_string(int(enc.address)) if enc.address else None) or "?"
228+
dtype = _try(lambda: _format_dtype(inner["dtype"]), "?")
229+
230+
head = f"{encoding}({dtype}, len={'?' if len_ is None else len_})"
231+
usize = _usize_size()
232+
try:
233+
strong = _read_usize(arc_inner_base)
234+
weak = _read_usize(arc_inner_base + usize) - 1
235+
return f"{head} @ {arc_inner_base:#x} [strong={strong}, weak={weak}]"
236+
except gdb.error:
237+
return f"{head} @ {arc_inner_base:#x}"
238+
239+
240+
def _format_typed_array(val: gdb.Value) -> str:
241+
try:
242+
return _format_array_ref(val["inner"])
243+
except (gdb.error, RuntimeError):
244+
return "Array(?)"
245+
246+
247+
class _Printer:
248+
def __init__(self, val: gdb.Value, fmt: Callable[[gdb.Value], str]):
249+
self._val, self._fmt = val, fmt
250+
251+
def to_string(self) -> str:
252+
return self._fmt(self._val)
253+
254+
255+
def _matches_dyn_plugin(name: str) -> bool:
256+
return (
257+
name in (_ARRAY_PLUGIN_FQN, _DYN_ARRAY_PLUGIN)
258+
or name.startswith(_DYN_ARRAY_PLUGIN + " +")
259+
or name.startswith(_DYN_ARRAY_PLUGIN + "+")
260+
)
261+
262+
263+
def _lookup_printer(val: gdb.Value) -> _Printer | None:
264+
try:
265+
t = val.type.strip_typedefs()
266+
except gdb.error:
267+
return None
268+
name = (t.name or "").strip()
269+
270+
if name == _ARRAY_REF_FQN:
271+
return _Printer(val, _format_array_ref)
272+
if name.startswith(_ARRAY_TYPED_PREFIX):
273+
return _Printer(val, _format_typed_array)
274+
if name == _ID_FQN:
275+
return _Printer(val, _format_id)
276+
if name.startswith("alloc::sync::Arc<") and _DYN_ARRAY_PLUGIN in name:
277+
return _Printer(val, _format_arc_dyn_plugin)
278+
if _matches_dyn_plugin(name):
279+
return _Printer(val, _format_dyn_plugin)
280+
if t.code in (gdb.TYPE_CODE_PTR, gdb.TYPE_CODE_REF):
281+
target = t.target()
282+
if target is not None and _matches_dyn_plugin((target.name or "").strip()):
283+
return _Printer(val, _format_dyn_plugin)
284+
return None
285+
286+
287+
def _install_at_front(container) -> None:
288+
if _lookup_printer in container:
289+
container.remove(_lookup_printer)
290+
container.insert(0, _lookup_printer)
291+
292+
293+
def register() -> None:
294+
# Install before rust-gdb's StdRcProvider, otherwise Arc<dyn T> from StdRc wins
295+
_install_at_front(gdb.pretty_printers)
296+
try:
297+
_install_at_front(gdb.current_progspace().pretty_printers)
298+
except (AttributeError, gdb.error):
299+
pass
300+
for of in gdb.objfiles():
301+
_install_at_front(of.pretty_printers)
302+
gdb.events.new_objfile.connect(lambda e: _install_at_front(e.new_objfile.pretty_printers))
303+
304+
305+
register()
306+
print("vortex_gdb: registered pretty-printers")

0 commit comments

Comments
 (0)