Last active
January 6, 2025 15:57
-
-
Save flga/30184a5b47a8b8ed0201fa33dd01dfe6 to your computer and use it in GitHub Desktop.
GDB pretty printers for Odin types
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Added support for
rune
,any
, andtypeid
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 fromtypeid
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.