Skip to content

Instantly share code, notes, and snippets.

@flga
Last active January 6, 2025 15:57
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)
@flga
Copy link
Author

flga commented Jan 4, 2025

Added support for rune, any, and typeid introspection.
Added some support for Microsoft's vscode extension.
Cleaned up and tightened up matchers.

any isn't doing much at the moment, the only value add is from typeid introspection.
I wanted to probe into full introspection but gdb makes it hard since it doesn't have a good api to iteratively build types, and no api at all for creating new types, so I abandoned it for now.

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