From d8fa25e3e74285e7882b68f980cc084e13da4c9b Mon Sep 17 00:00:00 2001 From: Dries Harnie Date: Tue, 25 Nov 2025 21:36:51 +0100 Subject: [PATCH] DEVTOOLS: Add LLDB pretty-printers --- devtools/lldb_pretty_printers.py | 206 +++++++++++++++++++++++++++++++ 1 file changed, 206 insertions(+) create mode 100644 devtools/lldb_pretty_printers.py diff --git a/devtools/lldb_pretty_printers.py b/devtools/lldb_pretty_printers.py new file mode 100644 index 00000000000..236705e3a50 --- /dev/null +++ b/devtools/lldb_pretty_printers.py @@ -0,0 +1,206 @@ +''' +This file contains LLDB pretty-printers for common ScummVM data types. +To enable it, add the following line to the .lldbinit file for this project: + + command script import devtools.lldb_pretty_printers + +LLDB disallows loading .lldbinit files from source directories by default, so +you may need to add the following to your global ~/.lldbinit: + + settings set target.load-cwd-lldbinit true + + +''' +from dataclasses import dataclass +import lldb + + +class ArrayProvider: + '''Formatter for Common::Array''' + def __init__(self, valobj, internal_dict): + self.valobj = valobj + self.data_type = self.valobj.GetChildMemberWithName("_storage").GetType().GetPointeeType() + self.data_size = self.data_type.GetByteSize() + self.update() + + def update(self): + self.size = \ + self.valobj.GetChildMemberWithName("_size").GetValueAsUnsigned() + return False + + def num_children(self, _max_children=None): + return self.size + + def has_children(self): + return self.size > 0 + + def get_child_index(self, name): + try: + return int(name.lstrip("[").rstrip("]")) + except ValueError: + return -1 + + def get_child_at_index(self, index): + if index < 0: + return None + if index >= self.num_children(): + return None + base = self.valobj.GetChildMemberWithName("_storage") + offset = index * self.data_size + return base.CreateChildAtOffset( + f"[{index}]", offset, self.data_type + ) + + +class HashMapProvider: + '''Formatter for Common::HashMap''' + def __init__(self, valobj, internal_dict): + self.valobj = valobj + self.update() + + def update(self): + self.children_with_values = [] + self.size = \ + self.valobj.GetChildMemberWithName("_size").GetValueAsUnsigned() + self.mask = \ + self.valobj.GetChildMemberWithName("_mask").GetValueAsUnsigned() + self.storage = self.valobj.GetChildMemberWithName("_storage") + for i in range(self.mask + 1): + child = self.storage.GetChildAtIndex(i, lldb.eNoDynamicValues, True) + if child.GetValueAsUnsigned() != 0: + self.children_with_values.append(child) + return False + + def num_children(self, _max_children=None): + return self.size + + def has_children(self): + return self.size > 0 + + def get_child_index(self, name): + return next((c for c in self.children_with_values if c.GetName() == name), -1) + + def get_child_at_index(self, index): + if index < 0: + return None + if index >= self.num_children(): + return None + + return self.children_with_values[index].Dereference() + + +class ListProvider: + '''Formatter for Common::List''' + def __init__(self, valobj, internal_dict): + self.valobj = valobj + self.element_type = self.valobj.GetType().GetTemplateArgumentType(0) + self.node_offset = self.valobj.GetType().FindDirectNestedType("NodeBase").GetByteSize() + self.update() + + def update(self): + self.anchor = self.valobj.GetChildMemberWithName("_anchor") + + self.size = 0 + cur = self.anchor + while True: + cur = cur.GetChildMemberWithName("_next").Dereference() + if cur.AddressOf().GetValueAsUnsigned() == self.anchor.AddressOf().GetValueAsUnsigned(): + break + self.size = self.size + 1 + return False + + def num_children(self, _max_children=None): + return self.size + + def has_children(self): + _prev = self.anchor.GetChildMemberWithName("_prev").GetValueAsUnsigned() + _next = self.anchor.AddressOf().GetValueAsUnsigned() + return _prev != _next + + def get_child_index(self, name): + try: + return int(name.lstrip("[").rstrip("]")) + except: + return -1 + + def get_child_at_index(self, index): + if index < 0 or index >= self.size: + return None + + cur = self.anchor + for _ in range(index): + cur = cur.GetChildMemberWithName("_next").Dereference() + + return cur.CreateChildAtOffset(f'[{index}]', self.node_offset, self.element_type) + + +def array_summary(valobj, internal): + size = valobj.GetNonSyntheticValue().GetChildMemberWithName('_size').GetValueAsUnsigned() + if size == 0: + return "(empty)" + if size == 1: + return "1 item" + return f"{size} items" + + +def construct_matrix_summary(rows, cols): + lines = [] + for r in range(rows): + line = " ".join(f'${{var._values[{r*cols+c}]}}' for c in range(cols)) + lines.append(line) + return '[' + "; ".join(lines) + ']' + +@dataclass(frozen=True) +class Pattern: + pattern: str + +summaries = { + 'Common::String': r'${var._str%s}', + + 'Common::Rect': r'(${var.left},${var.top}) -> (${var.right}, ${var.bottom})', + 'Math::Vector2d': r'(${var._values[0]}, ${var._values[1]})', + 'Math::Vector3d': r'(${var._values[0]}, ${var._values[1]}, ${var._values[2]})', + 'Math::Vector4d': r'(${var._values[0]}, ${var._values[1]}, ${var._values[2]}, ${var._values[3]})', + 'Math::Quaternion': r'(${var._values[0]}, ${var._values[1]}, ${var._values[2]}, ${var._values[3]})', + 'Math::Matrix4': construct_matrix_summary(4,4), + 'Math::Matrix3': construct_matrix_summary(3,3), + + 'Graphics::Surface': r'(${var.w},${var.h}) (bpp=${var.format.bytesPerPixel:d})', + Pattern('^Common::HashMap<.*>::Node'): r'\{key: ${var._key}, value: ${var._value}\}', + Pattern('^Common::Array<.*>$'): array_summary, + Pattern('^Common::List<.*>$'): r'${svar%#} items', +} + +providers = { + Pattern('^Common::Array<.*>$'): ArrayProvider, + Pattern('^Common::HashMap<.*>$'): HashMapProvider, + Pattern('^Common::List<.*>$'): ListProvider, +} + + +def __lldb_init_module(debugger, unused): + #lldb.formatters.Logger._lldb_formatters_debug_level = 2 + print('Loading ScummVM formatters...') + for klass, summary in summaries.items(): + if callable(summary): + summary = f'-F {__name__}.{summary.__name__}' + else: + summary = f'--summary-string "{summary}"' + + if isinstance(klass, Pattern): + debugger.HandleCommand( + f'type summary add -x "{klass.pattern}" --category scummvm {summary}' + ) + else: + debugger.HandleCommand( + f'type summary add "{klass}" --category scummvm {summary}' + ) + + for klass, provider in providers.items(): + debugger.HandleCommand( + f'type synthetic add -x --category scummvm --python-class {__name__}.{provider.__name__} "{klass.pattern}"' + ) + + debugger.HandleCommand( + r'type category enable scummvm' + )