From a5b925fdec9d0b84ab6d68c27541da8f70110152 Mon Sep 17 00:00:00 2001 From: Suraj Yadav Date: Thu, 30 Apr 2026 19:08:52 +0530 Subject: [PATCH] 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 --- .lldbinit | 1 + Meta/Debuggers/lldb/AK.py | 564 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 565 insertions(+) create mode 100644 .lldbinit create mode 100644 Meta/Debuggers/lldb/AK.py diff --git a/.lldbinit b/.lldbinit new file mode 100644 index 0000000000..021bf5ab8b --- /dev/null +++ b/.lldbinit @@ -0,0 +1 @@ +command script import ./Meta/Debuggers/lldb/AK.py diff --git a/Meta/Debuggers/lldb/AK.py b/Meta/Debuggers/lldb/AK.py new file mode 100644 index 0000000000..a18479322f --- /dev/null +++ b/Meta/Debuggers/lldb/AK.py @@ -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}")