# This file implements synthetic children providers and summaries for various Dear ImGui types for LLDB. # LLDB is used by Xcode, Android Studio, and may be used from VS Code, C++Builder, CLion, Eclipse etc. # # Useful links/documentation related to the feature: # - https://lldb.llvm.org/use/variable.html#summary-strings # - https://lldb.llvm.org/use/variable.html#synthetic-children # - https://lldb.llvm.org/python_reference/lldb-module.html # # To use it in a debug session: # > (lldb) command script import # # Alternatively you may include the above command in your ~/.lldbinit file to have the formatters # available in all future sessions import lldb class ArraySynthBase(object): """ Helper baseclass aimed to reduce the boilerplate needed for "array-like" containers """ def __init__(self, valobj, internal_dict): self.valobj = valobj def bind_to(self, pointer, size): array_p = pointer.GetType().GetPointeeType().GetArrayType(size).GetPointerType() self.array = pointer.Cast(array_p).Dereference() def update(self): self.array = self.valobj def num_children(self, max_children): return self.array.GetNumChildren(max_children) def get_child_index(self, name): return self.array.GetIndexOfChildWithName(name) def get_child_at_index(self, index): return self.array.GetChildAtIndex(index) def has_children(self): return self.array.MightHaveChildren() def get_value(self): return self.array class ImVectorSynth(ArraySynthBase): def update(self): self.size = self.valobj.GetChildMemberWithName("Size").GetValueAsUnsigned() self.capacity = self.valobj.GetChildMemberWithName("Capacity").GetValueAsUnsigned() data = self.valobj.GetChildMemberWithName("Data") self.bind_to(data, self.size) def get_summary(self): return f"Size={self.size} Capacity={self.capacity}" class ImSpanSynth(ArraySynthBase): def update(self): data = self.valobj.GetChildMemberWithName("Data") end = self.valobj.GetChildMemberWithName("DataEnd") element_size = data.GetType().GetPointeeType().GetByteSize() array_size = end.GetValueAsUnsigned() - data.GetValueAsUnsigned() self.size = int(array_size / element_size) self.bind_to(data, self.size) def get_summary(self): return f"Size={self.size}" class ImRectSummary(object): def __init__(self, valobj, internal_dict): self.valobj = valobj def update(self): pass def get_summary(self): min = self.valobj.GetChildMemberWithName("Min") max = self.valobj.GetChildMemberWithName("Max") minX = float(min.GetChildMemberWithName("x").GetValue()) minY = float(min.GetChildMemberWithName("y").GetValue()) maxX = float(max.GetChildMemberWithName("x").GetValue()) maxY = float(max.GetChildMemberWithName("y").GetValue()) return f"Min=({minX}, {minY}) Max=({maxX}, {maxY}) Size=({maxX - minX}, {maxY - minY})" def get_active_enum_flags(valobj): flag_set = set() enum_name = valobj.GetType().GetName() + "_" enum_type = valobj.GetTarget().FindFirstType(enum_name) if not enum_type.IsValid(): return flag_set enum_members = enum_type.GetEnumMembers() value = valobj.GetValueAsUnsigned() for i in range(0, enum_members.GetSize()): member = enum_members.GetTypeEnumMemberAtIndex(i) if value & member.GetValueAsUnsigned(): flag_set.add(member.GetName().removeprefix(enum_name)) return flag_set class ImGuiWindowSummary(object): def __init__(self, valobj, internal_dict): self.valobj = valobj def update(self): pass def get_summary(self): name = self.valobj.GetChildMemberWithName("Name").GetSummary() active = self.valobj.GetChildMemberWithName("Active").GetValueAsUnsigned() != 0 was_active = self.valobj.GetChildMemberWithName("WasActive").GetValueAsUnsigned() != 0 hidden = self.valobj.GetChildMemberWithName("Hidden") != 0 flags = get_active_enum_flags(self.valobj.GetChildMemberWithName("Flags")) active = 1 if active or was_active else 0 child = 1 if "ChildWindow" in flags else 0 popup = 1 if "Popup" in flags else 0 hidden = 1 if hidden else 0 return f"Name {name} Active {active} Child {child} Popup {popup} Hidden {hidden}" def __lldb_init_module(debugger, internal_dict): """ This function will be automatically called by LLDB when the module is loaded, here we register the various synthetics/summaries we have build before """ category_name = "imgui" category = debugger.GetCategory(category_name) # Make sure we don't accidentally keep accumulating languages or override the user's # category enablement in Xcode, where lldb-rpc-server loads this file once for eac # debugging session if not category.IsValid(): category = debugger.CreateCategory(category_name) category.AddLanguage(lldb.eLanguageTypeC_plus_plus) category.SetEnabled(True) def add_summary(typename, impl): summary = None if isinstance(impl, str): summary = lldb.SBTypeSummary.CreateWithSummaryString(impl) summary.SetOptions(lldb.eTypeOptionCascade) else: # Unfortunately programmatic summary string generation is an entirely different codepath # in LLDB. Register a convenient trampoline function which makes it look like it's part # of the SyntheticChildrenProvider contract summary = lldb.SBTypeSummary.CreateWithScriptCode(f''' synth = {impl.__module__}.{impl.__qualname__}(valobj.GetNonSyntheticValue(), internal_dict) synth.update() return synth.get_summary() ''') summary.SetOptions(lldb.eTypeOptionCascade | lldb.eTypeOptionFrontEndWantsDereference) category.AddTypeSummary(lldb.SBTypeNameSpecifier(typename, True), summary) def add_synthetic(typename, impl): add_summary(typename, impl) synthetic = lldb.SBTypeSynthetic.CreateWithClassName(f"{impl.__module__}.{impl.__qualname__}") synthetic.SetOptions(lldb.eTypeOptionCascade | lldb.eTypeOptionFrontEndWantsDereference) category.AddTypeSynthetic(lldb.SBTypeNameSpecifier(typename, True), synthetic) add_synthetic("^ImVector<.+>$", ImVectorSynth) add_synthetic("^ImSpan<.+>$", ImSpanSynth) add_summary("^ImVec2$", "x=${var.x} y=${var.y}") add_summary("^ImVec4$", "x=${var.x} y=${var.y} z=${var.z} w=${var.w}") add_summary("^ImRect$", ImRectSummary) add_summary("^ImGuiWindow$", ImGuiWindowSummary)