Skip to content

Instantly share code, notes, and snippets.

@flga
Last active May 12, 2025 05:56
Show Gist options
  • Save flga/30184a5b47a8b8ed0201fa33dd01dfe6 to your computer and use it in GitHub Desktop.
Save flga/30184a5b47a8b8ed0201fa33dd01dfe6 to your computer and use it in GitHub Desktop.
GDB pretty printers for Odin types
import gdb
import re
# Set it to true if you're using Microsoft's vscode plugin for c/c++.
# This works around the fact that it doesn't handle "map" display hints properly.
enable_msft_workarounds = False
class OdinAnyPrinter(gdb.ValuePrinter):
"Pretty print Odin any."
def __init__(self, val):
self.__val = val
def display_hint(self):
if enable_msft_workarounds:
return None
return "map"
def to_string(self):
return int(self.__val['id'])
def children(self):
yield ('id', self.__val['id'])
yield ('data', self.__val['data'])
def any_lookup_function(val):
if val.type.tag is None:
return None
if val.type.code != gdb.TYPE_CODE_STRUCT:
return None
if val.type.tag != "any":
return None
return OdinAnyPrinter(val)
gdb.pretty_printers.append(any_lookup_function)
class OdinTypeidPrinter(gdb.ValuePrinter):
"Pretty print Odin typeids."
def __init__(self, val):
self.__val = val
def display_hint(self):
if enable_msft_workarounds:
return None
return "map"
def to_string(self):
return int(self.__val)
def children(self):
id = int(self.__val)
mask = (1 << (8 * self.__val.type.sizeof - 8)) - 1
n = (id & mask)
sym = gdb.lookup_static_symbol("type_table")
type_table = sym.value()
yield ("type_info", (type_table['data'] + (id & mask)).dereference().dereference())
def typeid_lookup_function(val):
if val.type.name != "typeid":
return None
return OdinTypeidPrinter(val)
gdb.pretty_printers.append(typeid_lookup_function)
class OdinRunePrinter(gdb.ValuePrinter):
"Pretty print Odin runes."
def __init__(self, val):
self.__val = val
def to_string(self):
return f"{int(self.__val)} '{str(chr(int(self.__val)))}'"
def rune_lookup_function(val):
if val.type.name != "rune":
return None
return OdinRunePrinter(val)
gdb.pretty_printers.append(rune_lookup_function)
class OdinStringPrinter(gdb.ValuePrinter):
"Pretty print Odin strings."
def __init__(self, val):
self.__val = val
def display_hint(self):
return "string"
def to_string(self):
return self.__val["data"].string("utf-8", "ignore", int(self.__val["len"]))
def string_lookup_function(val):
if val.type.tag is None:
return None
if val.type.code != gdb.TYPE_CODE_STRUCT:
return None
if val.type.tag != "string":
return None
return OdinStringPrinter(val)
gdb.pretty_printers.append(string_lookup_function)
class OdinUnionPrinter(gdb.ValuePrinter):
"Pretty print Odin unions."
def __init__(self, val):
self.__val = val
self.__tag = int(val["tag"])
def display_hint(self):
if enable_msft_workarounds:
return None
return "map"
def to_string(self):
if self.__tag == 0:
return "<nil>"
return self.__val[f"v{self.__tag}"]
def children(self):
if self.__tag != 0:
yield ("value", self.__val[f"v{self.__tag}"])
def union_lookup_function(val):
if val.type.code != gdb.TYPE_CODE_UNION:
return None
has_tag = False
for f in val.type.fields():
if f.name == 'tag':
has_tag = True
elif not f.name.startswith('v'):
return None
if not has_tag:
return None
return OdinUnionPrinter(val)
gdb.pretty_printers.append(union_lookup_function)
class OdinSlicePrinter(gdb.ValuePrinter):
"Pretty print Odin slices."
def __init__(self, val):
self.__val = val
self.__len = int(self.__val["len"])
self.__data = self.__val["data"]
def display_hint(self):
return "array"
def to_string(self):
name = str(self.__val.type)
name = name[len("struct "):]
return f"{name} len = {self.__len}, data = 0x{int(self.__data):x}"
def child(self, i):
if i > self.__len:
return None
return (self.__data + i).dereference()
def num_children(self):
return self.__len
def children(self):
if self.__data.type.target().sizeof == 0:
return
for i in range(int(self.__len)):
yield (f"[{i}]", (self.__data + i).dereference())
def slice_lookup_function(val):
if val.type.tag is None:
return None
if val.type.code != gdb.TYPE_CODE_STRUCT:
return None
if not val.type.tag.startswith("[]"):
return None
return OdinSlicePrinter(val)
gdb.pretty_printers.append(slice_lookup_function)
class OdinSoaSlicePrinter(gdb.ValuePrinter):
"Pretty print Odin #soa slices."
def __init__(self, val):
self.__val = val
self.__len = int(self.__val["__$len"])
def display_hint(self):
if enable_msft_workarounds:
return None
return "map"
def to_string(self):
name = str(self.__val.type)
name = name[len("struct "):]
return f"{name} len = {self.__len}"
def num_children(self):
return len(self.__val.type.fields())
def children(self):
yield ("__$len", self.__len)
for field in self.__val.type.fields():
if field.name == "__$len":
continue
yield (field.name, self.__val[field.name].cast(field.type.target().array(self.__len - 1).pointer()).dereference())
def soa_slice_lookup_function(val):
if val.type.tag is None:
return None
if val.type.code != gdb.TYPE_CODE_STRUCT:
return None
if not val.type.tag.startswith("#soa[]"):
return None
return OdinSoaSlicePrinter(val)
gdb.pretty_printers.append(soa_slice_lookup_function)
class OdinDynamicArrayPrinter(gdb.ValuePrinter):
"Pretty print Odin dynamic arrays."
def __init__(self, val):
self.__val = val
self.__len = int(self.__val["len"])
self.__cap = int(self.__val["cap"])
self.__data = self.__val["data"]
def display_hint(self):
if enable_msft_workarounds:
return None
return "map"
def to_string(self):
name = str(self.__val.type)
name = name[len("struct "):]
return f"{name} len = {self.__len}, cap = {self.__cap}"
def num_children(self):
return 4
def children(self):
yield ("data", self.__data.cast(self.__data.type.target().array(self.__len - 1).pointer()).dereference())
yield ("len", self.__len)
yield ("cap", self.__cap)
yield ("allocator", self.__val["allocator"])
def soa_dynamic_array_lookup_function(val):
if val.type.tag is None:
return None
if val.type.code != gdb.TYPE_CODE_STRUCT:
return None
if not val.type.tag.startswith("[dynamic]"):
return None
return OdinDynamicArrayPrinter(val)
gdb.pretty_printers.append(soa_dynamic_array_lookup_function)
class OdinSoaDynamicArrayPrinter(gdb.ValuePrinter):
"Pretty print Odin #soa dynamic arrays."
def __init__(self, val):
self.__val = val
self.__len = int(self.__val["__$len"])
self.__cap = int(self.__val["__$cap"])
def display_hint(self):
if enable_msft_workarounds:
return None
return "map"
def to_string(self):
name = str(self.__val.type)
name = name[len("struct "):]
return f"{name} len = {self.__len}, cap = {self.__cap}"
def num_children(self):
return len(self.__val.type.fields())
def children(self):
for field in self.__val.type.fields():
if field.name == "__$len" or field.name == "__$cap" or field.name == "allocator":
continue
yield (field.name, self.__val[field.name].cast(field.type.target().array(self.__len - 1).pointer()).dereference())
yield ("__$len", self.__len)
yield ("__$cap", self.__cap)
yield ("allocator", self.__val["allocator"])
def soa_soa_dynamic_array_lookup_function(val):
if val.type.tag is None:
return None
if val.type.code != gdb.TYPE_CODE_STRUCT:
return None
if not val.type.tag.startswith("#soa[dynamic]"):
return None
return OdinSoaDynamicArrayPrinter(val)
gdb.pretty_printers.append(soa_soa_dynamic_array_lookup_function)
class OdinMapPrinter(gdb.ValuePrinter):
"Pretty print Odin maps."
def __init__(self, val):
self.__val = val
self.__len = val["len"]
self.__base_addr = int(val["data"].dereference().address) & ~63
cap_log2 = int(val["data"].dereference().address) & 63
self.__cap = 0 if cap_log2 <= 0 else 1 << cap_log2
self.__key_type = val["data"]["key"].type
self.__key_cell_type = val["data"]["key_cell"].type
self.__key_base_addr = self.__base_addr
self.__value_type = val["data"]["value"].type
self.__value_cell_type = val["data"]["value_cell"].type
self.__value_base_addr = self.__key_base_addr + self.map_cell_offset(self.__key_type, self.__key_cell_type, self.__cap)
self.__hash_type = val["data"]["hash"].type
self.__tombstone_mask = 1 << (self.__hash_type.sizeof * 8 - 1)
hash_addr = self.__value_base_addr + self.map_cell_offset(self.__value_type, self.__value_cell_type, self.__cap)
self.__hashes = gdb.Value(hash_addr).cast(self.__hash_type.pointer())
def display_hint(self):
if self.__value_type.sizeof == 0:
return "array"
if enable_msft_workarounds:
return None
return "map"
def num_children(self):
return self.__len
def children(self):
for i in range(self.__cap):
hash = (self.__hashes + i).dereference()
if hash == 0 or (hash & self.__tombstone_mask) != 0:
continue
key_ptr = self.__key_base_addr + self.map_cell_offset(self.__key_type, self.__key_cell_type, i)
key = gdb.Value(key_ptr).cast(self.__key_type.pointer()).dereference()
if self.__value_type.sizeof == 0:
yield (f"{i}", key)
else:
value_ptr = self.__value_base_addr + self.map_cell_offset(self.__value_type, self.__value_cell_type, i)
value = gdb.Value(value_ptr).cast(self.__value_type.pointer()).dereference()
yield (f"{key}", value)
def map_cell_offset(self, elem_type, elem_cell_type, index):
if elem_type.sizeof == 0:
return 0
elems_per_cell = elem_cell_type.sizeof // elem_type.sizeof
cell_index = index // elems_per_cell
data_index = index % elems_per_cell
return (cell_index * elem_cell_type.sizeof) + (data_index * elem_type.sizeof)
def map_lookup_function(val):
if val.type.tag is None:
return None
if val.type.code != gdb.TYPE_CODE_STRUCT:
return None
if not val.type.tag.startswith("map["):
return None
return OdinMapPrinter(val)
gdb.pretty_printers.append(map_lookup_function)
@crimeraaa
Copy link

Hi, just want to say thank you so much for this! I had a hard time following the implementation of map so the fact you have a working pretty printer right here is so useful to me.

just a small comment for anyone interested: gdb.ValuePrinter is only available starting with GDB 14 (see December 3rd, 2023 announcement). GDB on my WSL Debian installation is stuck at version 13 (ugh)- fortunately there's a workaround from GCC's own libstdcxx printers:

if hasattr(gdb, 'ValuePrinter'):
    printer_base = gdb.ValuePrinter
else:
    printer_base = object

Then we can just inherit from printer_base instead of gdb.ValuePrinter.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment