|
| 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