Meta: Add lldb pretty printers for AK types

fix string printer

Use lldb command to import printer

fix order and use nonsynthetic value

fix optional
This commit is contained in:
Suraj Yadav 2026-04-30 19:08:52 +05:30 committed by Sam Atkins
parent 3049d77979
commit a5b925fdec
2 changed files with 565 additions and 0 deletions

1
.lldbinit Normal file
View file

@ -0,0 +1 @@
command script import ./Meta/Debuggers/lldb/AK.py

564
Meta/Debuggers/lldb/AK.py Normal file
View file

@ -0,0 +1,564 @@
import lldb
def ak_atomic_summary(valobj, internal_dict):
value = valobj.GetChildMemberWithName("m_value")
return value.GetSummary() or value.GetValueAsUnsigned()
def ak_bytestring_summary(valobj, internal_dict):
impl_ptr = valobj.GetChildMemberWithName("m_impl").GetChildMemberWithName("m_ptr")
if impl_ptr.GetValueAsUnsigned() == 0:
return '""'
impl = impl_ptr.Dereference()
return ak_string_impl_summary(impl, internal_dict)
def ak_string_impl_summary(valobj, internal_dict):
length = valobj.GetChildMemberWithName("m_length").GetValueAsUnsigned()
if length == 0:
return '""'
data = valobj.GetChildMemberWithName("m_inline_buffer")
arr = data.GetPointeeData(0, length).uint8s
return bytes(arr).decode("utf-8", "replace")
def read_process_memory(process, address, byte_count):
if address == 0 or byte_count == 0:
return b""
error = lldb.SBError() # type: ignore
memory = process.ReadMemory(address, byte_count, error)
if error.Fail():
return None
return memory
def ak_distinct_numeric_summary(valobj, internal_dict):
val = valobj.GetNonSyntheticValue()
m_value = val.GetChildMemberWithName("m_value")
return m_value.GetValue() or m_value.GetSummary()
def ak_fixedarray_summary(valobj, internal_dict):
valobj = valobj.GetNonSyntheticValue()
size = valobj.GetChildMemberWithName("m_size").GetValueAsUnsigned()
return f"size={size}"
def ak_hashmap_summary(valobj, internal_dict):
valobj = valobj.GetNonSyntheticValue()
table = valobj.GetChildMemberWithName("m_table")
size = table.GetChildMemberWithName("m_size").GetValueAsUnsigned()
return f"size={size}"
def ak_nonnullrefptr_summary(valobj, internal_dict):
valobj = valobj.GetNonSyntheticValue()
m_ptr = valobj.GetChildMemberWithName("m_ptr")
ptr_value = m_ptr.GetValueAsUnsigned()
if ptr_value == 0:
return "nullptr"
pointee = m_ptr.Dereference()
if pointee.IsValid():
ref_count = pointee.GetChildMemberWithName("m_ref_count")
if ref_count.IsValid():
ref_count_val = ref_count.GetValueAsUnsigned()
return f"{hex(ptr_value)} (ref_count={ref_count_val})"
return hex(ptr_value)
return hex(ptr_value)
def ak_optional_summary(valobj, internal_dict):
valobj = valobj.GetNonSyntheticValue()
m_has_value = valobj.GetChildMemberWithName("m_has_value")
if m_has_value and m_has_value.IsValid():
return "Some" if m_has_value.GetValueAsUnsigned() != 0 else "None"
m_pointer = valobj.GetChildMemberWithName("m_pointer")
if m_pointer and m_pointer.IsValid():
return "Some" if m_pointer.GetValueAsUnsigned() != 0 else "None"
return None
def ak_ownptr_summary(valobj, internal_dict):
m_ptr = valobj.GetChildMemberWithName("m_ptr")
ptr_value = m_ptr.GetValueAsUnsigned()
if ptr_value == 0:
return "nullptr"
return hex(ptr_value)
def ak_refcounted_summary(valobj, internal_dict):
value = valobj.GetChildMemberWithName("m_ref_count")
return value.GetSummary() or value.GetValue()
def ak_refptr_summary(valobj, internal_dict):
valobj = valobj.GetNonSyntheticValue()
m_ptr = valobj.GetChildMemberWithName("m_ptr")
ptr_value = m_ptr.GetValueAsUnsigned()
if ptr_value == 0:
return "nullptr"
pointee = m_ptr.Dereference()
if pointee.IsValid():
ref_count = pointee.GetChildMemberWithName("m_ref_count")
if ref_count.IsValid():
ref_count_val = ref_count.GetValueAsUnsigned()
return f"{hex(ptr_value)} (ref_count={ref_count_val})"
return hex(ptr_value)
return hex(ptr_value)
def ak_singlylinkedlist_summary(valobj, internal_dict):
valobj = valobj.GetNonSyntheticValue()
m_head = valobj.GetChildMemberWithName("m_head")
size = 0
node = m_head
while node.GetValueAsUnsigned() != 0:
size += 1
next_ptr = node.Dereference().GetChildMemberWithName("next")
node = next_ptr
if size > 10000:
break
return f"size={size}"
def ak_string_summary(valobj, internal_dict):
valobj = valobj.GetNonSyntheticValue()
process = valobj.GetProcess()
if not process.IsValid():
return None
impl = valobj.GetChildMemberWithName("m_impl")
short_string = impl.GetChildMemberWithName("short_string")
short_string_tag = short_string.GetChildMemberWithName("byte_count_and_short_string_flag").GetValueAsUnsigned()
if short_string_tag & 1:
length = short_string_tag >> 2
if length == 0:
return '""'
storage = short_string.GetChildMemberWithName("storage")
bytes_start = storage.GetLoadAddress()
raw_bytes = read_process_memory(process, bytes_start, length)
if raw_bytes is None:
return None
return '"' + raw_bytes.decode("utf-8", "replace") + '"'
data_ptr = impl.GetChildMemberWithName("data")
if data_ptr.GetValueAsUnsigned() == 0:
return '""'
string_data = data_ptr.Dereference()
if not string_data.IsValid():
return None
if string_data.GetChildMemberWithName("m_substring").GetValueAsUnsigned() != 0:
return None
length = string_data.GetChildMemberWithName("m_byte_count").GetValueAsUnsigned()
if length == 0:
return '""'
bytes_start = string_data.GetChildMemberWithName("m_bytes_or_substring_data").GetLoadAddress()
raw_bytes = read_process_memory(process, bytes_start, length)
if raw_bytes is None:
return None
return '"' + raw_bytes.decode("utf-8", "replace") + '"'
def ak_stringview_summary(valobj, internal_dict):
length = valobj.GetChildMemberWithName("m_length").GetValueAsUnsigned()
if length == 0:
return '""'
characters = valobj.GetChildMemberWithName("m_characters")
arr = characters.GetPointeeData(0, length).uint8s
return '"' + bytes(arr).decode("utf-8", "replace") + '"'
def ak_variant_summary(valobj, internal_dict):
valobj = valobj.GetNonSyntheticValue()
index = valobj.GetChildMemberWithName("m_index").GetValueAsUnsigned()
ty = valobj.GetType()
current_type = ty.GetTemplateArgumentType(index)
if current_type.IsValid():
return f"{current_type.GetName()}"
return "AK::Variant<?>"
def ak_vector_summary(valobj, internal_dict):
valobj = valobj.GetNonSyntheticValue()
m_size = valobj.GetChildMemberWithName("m_size")
size = m_size.GetValueAsUnsigned()
return f"size={size}"
class AKArraySyntheticProvider:
def __init__(self, valobj, internal_dict):
self.valobj = valobj.GetNonSyntheticValue()
self.update()
def update(self):
valType = self.valobj.GetType()
self.elem_type = valType.GetTemplateArgumentType(0)
self.size = valType.GetTemplateArgumentValue(self.valobj.GetTarget(), 1).GetValueAsUnsigned()
if self.size == 0:
self.data_ptr = None
return
data = self.valobj.GetChildMemberWithName("__data")
self.data_ptr = data.AddressOf().Cast(self.elem_type.GetPointerType())
self.elem_size = self.elem_type.GetByteSize()
def has_children(self):
return self.size > 0
def num_children(self):
return self.size
def get_child_index(self, name):
if name.startswith("[") and name.endswith("]"):
try:
return int(name[1:-1])
except (ValueError, TypeError):
return -1
return -1
def get_child_at_index(self, index):
if index < 0 or index >= self.size or self.data_ptr is None:
return None
return self.data_ptr.CreateChildAtOffset(f"[{index}]", index * self.elem_size, self.elem_type)
class AKFixedArraySynthProvider:
def __init__(self, valobj, internal_dict):
self.valobj = valobj.GetNonSyntheticValue()
self.update()
def update(self):
self.size = self.valobj.GetChildMemberWithName("m_size").GetValueAsUnsigned()
self.elements = self.valobj.GetChildMemberWithName("m_elements")
self.element_type = self.elements.GetType().GetPointeeType()
self.element_size = self.element_type.GetByteSize()
def has_children(self):
return self.size > 0
def num_children(self):
return self.size
def get_child_index(self, name):
if not name.startswith("[") or not name.endswith("]"):
return -1
try:
return int(name[1:-1])
except ValueError:
return -1
def get_child_at_index(self, index):
if index < 0 or index >= self.num_children():
return None
if self.elements.GetValueAsUnsigned() == 0:
return None
return self.elements.CreateChildAtOffset(
f"[{index}]",
index * self.element_size,
self.element_type,
)
class AKHashMapSynthProvider:
def __init__(self, valobj, internal_dict):
self.valobj = valobj.GetNonSyntheticValue()
self.entries = []
self.update()
def update(self):
self.entries = []
table = self.valobj.GetChildMemberWithName("m_table")
buckets = table.GetChildMemberWithName("m_buckets")
size = table.GetChildMemberWithName("m_size").GetValueAsUnsigned()
mask = table.GetChildMemberWithName("m_mask").GetValueAsUnsigned()
if size == 0 or buckets.GetValueAsUnsigned() == 0:
return
entry_type = table.GetType().GetTemplateArgumentType(0)
bucket_type = buckets.GetType().GetPointeeType()
bucket_size = bucket_type.GetByteSize()
target = self.valobj.GetTarget()
for i in range(mask + 1):
if len(self.entries) >= size:
break
bucket = buckets.CreateChildAtOffset(
f"bucket[{i}]",
i * bucket_size,
bucket_type,
)
state = bucket.GetChildMemberWithName("state").GetValueAsUnsigned()
if state == 0:
continue
storage = bucket.GetChildMemberWithName("storage")
entry = bucket.CreateValueFromAddress(
f"entry[{len(self.entries)}]",
storage.GetAddress().GetLoadAddress(target),
entry_type,
)
self.entries.append(entry)
def has_children(self):
return len(self.entries) > 0
def num_children(self):
return len(self.entries)
def get_child_index(self, name):
if name.startswith("[") and name.endswith("]"):
try:
return int(name[1:-1])
except ValueError:
return -1
return -1
def get_child_at_index(self, index):
if index < 0 or index >= len(self.entries):
return None
entry = self.entries[index]
key = entry.GetChildMemberWithName("key")
val = entry.GetChildMemberWithName("value")
key_str = key.GetSummary() or key.GetValue() or "?"
key_str = key_str.strip('"')
return val.Clone(f"[{key_str}]")
class AKIntrusiveListSyntheticProvider:
def __init__(self, valobj, internal_dict):
self.valobj = valobj
self.update()
def update(self):
self.val = self.valobj.GetNonSyntheticValue()
t = self.val.GetType()
self.element_type = t.GetTemplateArgumentType(0)
self.node_member_offset = 0
for i in range(self.element_type.GetNumberOfFields()):
field = self.element_type.GetFieldAtIndex(i)
if "IntrusiveListNode" in field.GetType().GetName():
self.node_member_offset = field.GetOffsetInBytes()
break
self.first = self.val.GetChildMemberWithName("m_storage").GetChildMemberWithName("m_first")
def _node_to_value(self, node):
addr = node.GetValueAsUnsigned() - self.node_member_offset
return self.val.CreateValueFromAddress(None, addr, self.element_type)
def num_children(self):
count = 0
current = self.first
while current.GetValueAsUnsigned() != 0:
count += 1
current = current.GetChildMemberWithName("m_next")
return count
def get_child_at_index(self, index):
current = self.first
i = 0
while current.GetValueAsUnsigned() != 0:
if i == index:
val = self._node_to_value(current)
return val.Clone(f"[{i}]")
current = current.GetChildMemberWithName("m_next")
i += 1
return None
class AKSinglyLinkedListSyntheticProvider:
def __init__(self, valobj, internal_dict):
self.valobj = valobj
self.update()
def update(self):
self.val = self.valobj.GetNonSyntheticValue()
self.head = self.val.GetChildMemberWithName("m_head")
def has_children(self):
return self.head.GetValueAsUnsigned() != 0
def num_children(self):
count = 0
node = self.head
while node.GetValueAsUnsigned() != 0:
count += 1
node = node.GetChildMemberWithName("next")
return count
def get_child_at_index(self, index):
node = self.head
i = 0
while node.GetValueAsUnsigned() != 0:
if i == index:
val = node.GetChildMemberWithName("value")
return val.Clone(f"[{i}]")
node = node.GetChildMemberWithName("next")
i += 1
return None
def get_child_index(self, name):
if name.startswith("[") and name.endswith("]"):
try:
return int(name[1:-1])
except (ValueError, TypeError):
return -1
return -1
class AKVariantSyntheticProvider:
def __init__(self, valobj, internal_dict):
self.valobj = valobj
self.update()
def update(self):
self.val = self.valobj.GetNonSyntheticValue()
self.index = self.val.GetChildMemberWithName("m_index").GetValueAsUnsigned()
self.data = self.val.GetChildMemberWithName("m_data")
self.type = self.val.GetType()
self.num_types = self.type.GetNumberOfTemplateArguments()
self.active_type = None
if self.index < self.num_types:
self.active_type = self.type.GetTemplateArgumentType(self.index)
def has_children(self):
return self.active_type is not None
def num_children(self):
return 1 if self.active_type else 0
def get_child_at_index(self, index):
if index != 0 or not self.active_type:
return None
data_addr = self.data.GetAddress().GetLoadAddress(self.val.GetTarget())
value = self.val.CreateValueFromAddress("value", data_addr, self.active_type)
return value.Clone("value")
def get_child_index(self, name):
return 0 if name == "value" else -1
class AKVectorSynthProvider:
def __init__(self, valobj, internal_dict):
self.valobj = valobj.GetNonSyntheticValue()
self.update()
def update(self):
self.m_size = self.valobj.GetChildMemberWithName("m_size")
self.size = self.m_size.GetValueAsUnsigned()
self.elements = []
self.element_type = None
if self.size == 0:
return
m_metadata = self.valobj.GetChildMemberWithName("m_metadata")
outline_buffer = m_metadata.GetChildMemberWithName("outline_buffer")
self.element_type = self.valobj.GetType().GetTemplateArgumentType(0)
if outline_buffer.GetValueAsUnsigned() != 0:
elements_ptr = outline_buffer.Cast(self.element_type.GetPointerType())
self.elements_ptr = elements_ptr
else:
inline_buffer = self.valobj.GetChildMemberWithName("m_inline_buffer_storage")
self.elements_ptr = inline_buffer.Cast(self.element_type.GetPointerType())
def has_children(self):
return self.size > 0
def num_children(self):
return self.size
def get_child_index(self, name):
if name.startswith("[") and name.endswith("]"):
try:
return int(name[1:-1])
except ValueError:
return -1
return -1
def get_child_at_index(self, index):
if index < 0 or index >= self.size:
return None
assert self.element_type is not None
return self.elements_ptr.CreateChildAtOffset(
f"[{index}]",
index * self.element_type.GetByteSize(),
self.element_type,
)
def __lldb_init_module(debugger, internal_dict):
commands = [
'type synthetic add -x "^AK::Array(<.*>)?$" -l AK.AKArraySyntheticProvider',
'type summary add -x "^AK::Atomic(<.*>)?$" -F AK.ak_atomic_summary',
'type summary add -x "^AK::ByteString(<.*>)?$" -F AK.ak_bytestring_summary',
'type summary add -x "^AK::ByteStringImpl(<.*>)?$" -F AK.ak_string_impl_summary',
'type summary add -x "^AK::DistinctNumeric(<.*>)?$" -F AK.ak_distinct_numeric_summary',
'type summary add -x "^AK::FixedArray(<.*>)?$" -F AK.ak_fixedarray_summary',
'type synthetic add -x "^AK::FixedArray(<.*>)?$" -l AK.AKFixedArraySynthProvider',
'type summary add -x "^AK::HashMap(<.*>)?$" -F AK.ak_hashmap_summary',
'type synthetic add -x "^AK::HashMap(<.*>)?$" -l AK.AKHashMapSynthProvider',
'type synthetic add -x "^AK::IntrusiveList(<.*>)?$" -l AK.AKIntrusiveListSyntheticProvider',
'type summary add -x "^AK::NonnullRefPtr(<.*>)?$" -F AK.ak_nonnullrefptr_summary',
'type summary add -x "^AK::Optional(<.*>)?$" -F AK.ak_optional_summary',
'type summary add -x "^AK::OwnPtr(<.*>)?$" -F AK.ak_ownptr_summary',
'type summary add -x "^AK::RefCounted(<.*>)?$" -F AK.ak_refcounted_summary',
'type summary add -x "^AK::RefPtr(<.*>)?$" -F AK.ak_refptr_summary',
'type summary add -x "^AK::SinglyLinkedList(<.*>)?$" -F AK.ak_singlylinkedlist_summary',
'type summary add -x "^AK::String(<.*>)?$" -F AK.ak_string_summary',
'type summary add -x "^AK::StringView(<.*>)?$" -F AK.ak_stringview_summary',
'type summary add -x "^AK::Variant(<.*>)?$" -F AK.ak_variant_summary',
'type summary add -x "^AK::Vector(<.*>)?$" -F AK.ak_vector_summary',
'type synthetic add -x "^AK::Variant(<.*>)?$" -l AK.AKVariantSyntheticProvider',
'type synthetic add -x "^AK::Vector(<.*>)?$" -l AK.AKVectorSynthProvider',
'type synthetic add -x "^AK::SinglyLinkedList(<.*>)?$" -l AK.AKSinglyLinkedListSyntheticProvider',
]
for cmd in commands:
try:
debugger.HandleCommand(cmd)
except Exception as e:
print(f"ak.py: Failed to run command: {cmd}")
print(f" Error: {e}")