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)
diff --git a/lib/smol_str/src/gdb_smolstr_printer.py b/lib/smol_str/src/gdb_smolstr_printer.py
new file mode 100644
index 0000000..9b2984f
--- /dev/null
+++ b/lib/smol_str/src/gdb_smolstr_printer.py
@@ -0,0 +1,137 @@
+# Pretty printer for smol_str::SmolStr
+#
+# Usage (any of these):
+#   (gdb) source /path/to/gdb_smolstr_printer.py
+# or add to .gdbinit
+#   python
+#   import gdb
+#   gdb.execute("source /path/to/gdb_smolstr_printer.py")
+#   end
+#
+# After loading:
+#   (gdb) info pretty-printer
+#     ...
+#     global pretty-printers:
+#       smol_str
+#         SmolStr
+#
+# Disable/enable:
+#   (gdb) disable pretty-printer global smol_str SmolStr
+#   (gdb) enable  pretty-printer global smol_str SmolStr
+
+import gdb
+import gdb.printing
+import re
+
+SMOL_INLINE_SIZE_RE = re.compile(r".*::_V(\d+)$")
+
+def _read_utf8(mem):
+    try:
+        return mem.tobytes().decode("utf-8", errors="replace")
+    except Exception:
+        return repr(mem.tobytes())
+
+def _active_variant(enum_val):
+    """Return (variant_name, variant_value) for a Rust enum value using discriminant logic.
+       Assume layout: fields[0] is unnamed u8 discriminant; fields[1] is the active variant.
+    """
+    fields = enum_val.type.fields()
+    if len(fields) < 2:
+        return None, None
+    variant_field = fields[1]
+    return variant_field.name, enum_val[variant_field]
+
+class SmolStrProvider:
+    def __init__(self, val):
+        self.val = val
+
+    def to_string(self):
+        try:
+            repr_enum = self.val["__0"]
+        except Exception:
+            return "<SmolStr: missing __0>"
+
+        variant_name, variant_val = _active_variant(repr_enum)
+        if not variant_name:
+            return "<SmolStr: unknown variant>"
+
+        if variant_name == "Inline":
+            try:
+                inline_len_val = variant_val["len"]
+                m = SMOL_INLINE_SIZE_RE.match(str(inline_len_val))
+                if not m:
+                    return "<SmolStr Inline: bad len>"
+                length = int(m.group(1))
+                buf = variant_val["buf"]
+                data = bytes(int(buf[i]) for i in range(length))
+                return data.decode("utf-8", errors="replace")
+            except Exception as e:
+                return f"<SmolStr Inline error: {e}>"
+
+        if variant_name == "Static":
+            try:
+                data_ptr = variant_val["data_ptr"]
+                length = int(variant_val["length"])
+                mem = gdb.selected_inferior().read_memory(int(data_ptr), length)
+                return _read_utf8(mem)
+            except Exception as e:
+                return f"<SmolStr Static error: {e}>"
+
+        if variant_name == "Heap":
+            try:
+                # variant_val is an Arc<str>
+                inner = variant_val["__0"]["ptr"]["pointer"]
+                # inner is a fat pointer to ArcInner<str>
+                data_ptr = inner["data_ptr"]
+                length = int(inner["length"])
+                # ArcInner layout:
+                # strong: Atomic<usize>, weak: Atomic<usize> | unsized tail 'data' bytes.
+                sizeof_AtomicUsize = gdb.lookup_type("core::sync::atomic::AtomicUsize").sizeof
+                header_size = sizeof_AtomicUsize * 2       # strong + weak counters
+                data_arr = int(data_ptr) + header_size
+                mem = gdb.selected_inferior().read_memory(data_arr, length)
+                return _read_utf8(mem)
+            except Exception as e:
+                return f"<SmolStr Heap error: {e}>"
+
+        return f"<SmolStr: unhandled variant {variant_name}>"
+
+    def display_hint(self):
+        return "string"
+
+class SmolStrSubPrinter(gdb.printing.SubPrettyPrinter):
+    def __init__(self):
+        super(SmolStrSubPrinter, self).__init__("SmolStr")
+
+    def __call__(self, val):
+        if not self.enabled:
+            return None
+        try:
+            t = val.type.strip_typedefs()
+            if t.code == gdb.TYPE_CODE_STRUCT and t.name == "smol_str::SmolStr":
+                return SmolStrProvider(val)
+        except Exception:
+            pass
+        return None
+
+class SmolStrPrettyPrinter(gdb.printing.PrettyPrinter):
+    def __init__(self):
+        super(SmolStrPrettyPrinter, self).__init__("smol_str", [])
+        self.subprinters = []
+        self._sp = SmolStrSubPrinter()
+        self.subprinters.append(self._sp)
+
+    def __call__(self, val):
+        # Iterate subprinters (only one now, scalable for future)
+        for sp in self.subprinters:
+            pp = sp(val)
+            if pp is not None:
+                return pp
+        return None
+
+printer = SmolStrPrettyPrinter()
+
+def register_printers(objfile=None):
+    gdb.printing.register_pretty_printer(objfile, printer, replace=True)
+
+register_printers()
diff --git a/lib/smol_str/src/lib.rs b/lib/smol_str/src/lib.rs
index 582ea2e..0d1f01a 100644
--- a/lib/smol_str/src/lib.rs
+++ b/lib/smol_str/src/lib.rs
@@ -1,5 +1,6 @@
 #![cfg_attr(not(feature = "std"), no_std)]
 #![cfg_attr(docsrs, feature(doc_cfg))]
+#![debugger_visualizer(gdb_script_file = "gdb_smolstr_printer.py")]
 
 extern crate alloc;