Skip to content

Commit 3712013

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 3712013

File tree

2 files changed

+139
-0
lines changed

2 files changed

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