Skip to content

Commit c58810b

Browse files
committed
Provide a gdb pretty printer for smol_str::SmolStr
Auto-loaded via the debugger_visualizer attribute. Tested on smolstr's unittest: $ RUSTFLAGS="-C debuginfo=2 -C opt-level=0" cargo test -p smol_str --no-run $ rust-gdb target/debug/deps/test-a806b111557a7133 (gdb) break test::conversions (gdb) run (gdb) next (gdb) print s (and other locations in that file, to test the three cases: Inline, Static and Heap)
1 parent f3cbd0e commit c58810b

File tree

2 files changed

+140
-0
lines changed

2 files changed

+140
-0
lines changed
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
# Pretty printer for smol_str::SmolStr
2+
#
3+
# Usage (any of these):
4+
# (gdb) source /path/to/gdb_smolstr_printer.py
5+
# or add to .gdbinit
6+
# python
7+
# import gdb
8+
# gdb.execute("source /path/to/gdb_smolstr_printer.py")
9+
# end
10+
#
11+
# After loading:
12+
# (gdb) info pretty-printer
13+
# ...
14+
# global pretty-printers:
15+
# smol_str
16+
# SmolStr
17+
#
18+
# Disable/enable:
19+
# (gdb) disable pretty-printer global smol_str SmolStr
20+
# (gdb) enable pretty-printer global smol_str SmolStr
21+
22+
import gdb
23+
import gdb.printing
24+
import re
25+
26+
SMOL_INLINE_SIZE_RE = re.compile(r".*::_V(\d+)$")
27+
28+
def _read_utf8(mem):
29+
try:
30+
return mem.tobytes().decode("utf-8", errors="replace")
31+
except Exception:
32+
return repr(mem.tobytes())
33+
34+
def _active_variant(enum_val):
35+
"""Return (variant_name, variant_value) for a Rust enum value using discriminant logic.
36+
Assume layout: fields[0] is unnamed u8 discriminant; fields[1] is the active variant.
37+
"""
38+
fields = enum_val.type.fields()
39+
if len(fields) < 2:
40+
return None, None
41+
variant_field = fields[1]
42+
return variant_field.name, enum_val[variant_field]
43+
44+
class SmolStrProvider:
45+
def __init__(self, val):
46+
self.val = val
47+
48+
def to_string(self):
49+
try:
50+
repr_enum = self.val["__0"]
51+
except Exception:
52+
return "<SmolStr: missing __0>"
53+
54+
variant_name, variant_val = _active_variant(repr_enum)
55+
if not variant_name:
56+
return "<SmolStr: unknown variant>"
57+
58+
if variant_name == "Inline":
59+
try:
60+
inline_len_val = variant_val["len"]
61+
m = SMOL_INLINE_SIZE_RE.match(str(inline_len_val))
62+
if not m:
63+
return "<SmolStr Inline: bad len>"
64+
length = int(m.group(1))
65+
buf = variant_val["buf"]
66+
data = bytes(int(buf[i]) for i in range(length))
67+
return data.decode("utf-8", errors="replace")
68+
except Exception as e:
69+
return f"<SmolStr Inline error: {e}>"
70+
71+
if variant_name == "Static":
72+
try:
73+
data_ptr = variant_val["data_ptr"]
74+
length = int(variant_val["length"])
75+
mem = gdb.selected_inferior().read_memory(int(data_ptr), length)
76+
return _read_utf8(mem)
77+
except Exception as e:
78+
return f"<SmolStr Static error: {e}>"
79+
80+
if variant_name == "Heap":
81+
try:
82+
# variant_val is an Arc<str>
83+
inner = variant_val["__0"]["ptr"]["pointer"]
84+
# inner is a fat pointer to ArcInner<str>
85+
data_ptr = inner["data_ptr"]
86+
length = int(inner["length"])
87+
# ArcInner layout:
88+
# strong: Atomic<usize>, weak: Atomic<usize> | unsized tail 'data' bytes.
89+
sizeof_AtomicUsize = gdb.lookup_type("core::sync::atomic::AtomicUsize").sizeof
90+
header_size = sizeof_AtomicUsize * 2 # strong + weak counters
91+
data_arr = int(data_ptr) + header_size
92+
mem = gdb.selected_inferior().read_memory(data_arr, length)
93+
return _read_utf8(mem)
94+
except Exception as e:
95+
return f"<SmolStr Heap error: {e}>"
96+
97+
return f"<SmolStr: unhandled variant {variant_name}>"
98+
99+
def display_hint(self):
100+
return "string"
101+
102+
class SmolStrSubPrinter(gdb.printing.SubPrettyPrinter):
103+
def __init__(self):
104+
super(SmolStrSubPrinter, self).__init__("SmolStr")
105+
106+
def __call__(self, val):
107+
if not self.enabled:
108+
return None
109+
try:
110+
t = val.type.strip_typedefs()
111+
if t.code == gdb.TYPE_CODE_STRUCT and t.name == "smol_str::SmolStr":
112+
return SmolStrProvider(val)
113+
except Exception:
114+
pass
115+
return None
116+
117+
class SmolStrPrettyPrinter(gdb.printing.PrettyPrinter):
118+
def __init__(self):
119+
super(SmolStrPrettyPrinter, self).__init__("smol_str", [])
120+
self.subprinters = []
121+
self._sp = SmolStrSubPrinter()
122+
self.subprinters.append(self._sp)
123+
124+
def __call__(self, val):
125+
# Iterate subprinters (only one now, scalable for future)
126+
for sp in self.subprinters:
127+
pp = sp(val)
128+
if pp is not None:
129+
return pp
130+
return None
131+
132+
printer = SmolStrPrettyPrinter()
133+
134+
def register_printers(objfile=None):
135+
gdb.printing.register_pretty_printer(objfile, printer, replace=True)
136+
137+
register_printers()

lib/smol_str/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -963,3 +963,6 @@ fn from_buf_and_chars_size_hinted_heap() {
963963

964964
assert_eq!(str, "abcdefghijklmnopqr_0x1x2x3x4x5x6x7x8x9x10x11x12x13");
965965
}
966+
967+
#[debugger_visualizer(gdb_script_file = "gdb_smolstr_printer.py")]
968+
mod smolstr_gdb_visualizer {}

0 commit comments

Comments
 (0)