Initial commit
This commit is contained in:
206
devtools/lldb_pretty_printers.py
Normal file
206
devtools/lldb_pretty_printers.py
Normal file
@@ -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'
|
||||
)
|
||||
Reference in New Issue
Block a user