| 1 | # This file implements synthetic children providers and summaries for various Dear ImGui types for LLDB. |
| 2 | # LLDB is used by Xcode, Android Studio, and may be used from VS Code, C++Builder, CLion, Eclipse etc. |
| 3 | |
| 4 | # |
| 5 | # Useful links/documentation related to the feature: |
| 6 | # - https://lldb.llvm.org/use/variable.html#summary-strings |
| 7 | # - https://lldb.llvm.org/use/variable.html#synthetic-children |
| 8 | # - https://lldb.llvm.org/python_reference/lldb-module.html |
| 9 | # |
| 10 | # To use it in a debug session: |
| 11 | # > (lldb) command script import <path-to-this-file> |
| 12 | # |
| 13 | # Alternatively you may include the above command in your ~/.lldbinit file to have the formatters |
| 14 | # available in all future sessions |
| 15 | |
| 16 | import lldb |
| 17 | |
| 18 | class ArraySynthBase(object): |
| 19 | """ |
| 20 | Helper baseclass aimed to reduce the boilerplate needed for "array-like" containers |
| 21 | """ |
| 22 | |
| 23 | def __init__(self, valobj, internal_dict): |
| 24 | self.valobj = valobj |
| 25 | |
| 26 | def bind_to(self, pointer, size): |
| 27 | array_p = pointer.GetType().GetPointeeType().GetArrayType(size).GetPointerType() |
| 28 | self.array = pointer.Cast(array_p).Dereference() |
| 29 | |
| 30 | def update(self): |
| 31 | self.array = self.valobj |
| 32 | |
| 33 | def num_children(self, max_children): |
| 34 | return self.array.GetNumChildren(max_children) |
| 35 | |
| 36 | def get_child_index(self, name): |
| 37 | return self.array.GetIndexOfChildWithName(name) |
| 38 | |
| 39 | def get_child_at_index(self, index): |
| 40 | return self.array.GetChildAtIndex(index) |
| 41 | |
| 42 | def has_children(self): |
| 43 | return self.array.MightHaveChildren() |
| 44 | |
| 45 | def get_value(self): |
| 46 | return self.array |
| 47 | |
| 48 | class ImVectorSynth(ArraySynthBase): |
| 49 | def update(self): |
| 50 | self.size = self.valobj.GetChildMemberWithName("Size").GetValueAsUnsigned() |
| 51 | self.capacity = self.valobj.GetChildMemberWithName("Capacity").GetValueAsUnsigned() |
| 52 | |
| 53 | data = self.valobj.GetChildMemberWithName("Data") |
| 54 | |
| 55 | self.bind_to(data, self.size) |
| 56 | |
| 57 | def get_summary(self): |
| 58 | return f"Size={self.size} Capacity={self.capacity}" |
| 59 | |
| 60 | class ImSpanSynth(ArraySynthBase): |
| 61 | def update(self): |
| 62 | data = self.valobj.GetChildMemberWithName("Data") |
| 63 | end = self.valobj.GetChildMemberWithName("DataEnd") |
| 64 | |
| 65 | element_size = data.GetType().GetPointeeType().GetByteSize() |
| 66 | array_size = end.GetValueAsUnsigned() - data.GetValueAsUnsigned() |
| 67 | |
| 68 | self.size = int(array_size / element_size) |
| 69 | |
| 70 | self.bind_to(data, self.size) |
| 71 | |
| 72 | def get_summary(self): |
| 73 | return f"Size={self.size}" |
| 74 | |
| 75 | class ImRectSummary(object): |
| 76 | def __init__(self, valobj, internal_dict): |
| 77 | self.valobj = valobj |
| 78 | |
| 79 | def update(self): |
| 80 | pass |
| 81 | |
| 82 | def get_summary(self): |
| 83 | min = self.valobj.GetChildMemberWithName("Min") |
| 84 | max = self.valobj.GetChildMemberWithName("Max") |
| 85 | |
| 86 | minX = float(min.GetChildMemberWithName("x").GetValue()) |
| 87 | minY = float(min.GetChildMemberWithName("y").GetValue()) |
| 88 | |
| 89 | maxX = float(max.GetChildMemberWithName("x").GetValue()) |
| 90 | maxY = float(max.GetChildMemberWithName("y").GetValue()) |
| 91 | |
| 92 | return f"Min=({minX}, {minY}) Max=({maxX}, {maxY}) Size=({maxX - minX}, {maxY - minY})" |
| 93 | |
| 94 | def get_active_enum_flags(valobj): |
| 95 | flag_set = set() |
| 96 | |
| 97 | enum_name = valobj.GetType().GetName() + "_" |
| 98 | enum_type = valobj.GetTarget().FindFirstType(enum_name) |
| 99 | |
| 100 | if not enum_type.IsValid(): |
| 101 | return flag_set |
| 102 | |
| 103 | enum_members = enum_type.GetEnumMembers() |
| 104 | value = valobj.GetValueAsUnsigned() |
| 105 | |
| 106 | for i in range(0, enum_members.GetSize()): |
| 107 | member = enum_members.GetTypeEnumMemberAtIndex(i) |
| 108 | |
| 109 | if value & member.GetValueAsUnsigned(): |
| 110 | flag_set.add(member.GetName().removeprefix(enum_name)) |
| 111 | |
| 112 | return flag_set |
| 113 | |
| 114 | class ImGuiWindowSummary(object): |
| 115 | def __init__(self, valobj, internal_dict): |
| 116 | self.valobj = valobj |
| 117 | |
| 118 | def update(self): |
| 119 | pass |
| 120 | |
| 121 | def get_summary(self): |
| 122 | name = self.valobj.GetChildMemberWithName("Name").GetSummary() |
| 123 | |
| 124 | active = self.valobj.GetChildMemberWithName("Active").GetValueAsUnsigned() != 0 |
| 125 | was_active = self.valobj.GetChildMemberWithName("WasActive").GetValueAsUnsigned() != 0 |
| 126 | hidden = self.valobj.GetChildMemberWithName("Hidden") != 0 |
| 127 | |
| 128 | flags = get_active_enum_flags(self.valobj.GetChildMemberWithName("Flags")) |
| 129 | |
| 130 | active = 1 if active or was_active else 0 |
| 131 | child = 1 if "ChildWindow" in flags else 0 |
| 132 | popup = 1 if "Popup" in flags else 0 |
| 133 | hidden = 1 if hidden else 0 |
| 134 | |
| 135 | return f"Name {name} Active {active} Child {child} Popup {popup} Hidden {hidden}" |
| 136 | |
| 137 | |
| 138 | def __lldb_init_module(debugger, internal_dict): |
| 139 | """ |
| 140 | This function will be automatically called by LLDB when the module is loaded, here |
| 141 | we register the various synthetics/summaries we have build before |
| 142 | """ |
| 143 | |
| 144 | category_name = "imgui" |
| 145 | category = debugger.GetCategory(category_name) |
| 146 | |
| 147 | # Make sure we don't accidentally keep accumulating languages or override the user's |
| 148 | # category enablement in Xcode, where lldb-rpc-server loads this file once for eac |
| 149 | # debugging session |
| 150 | if not category.IsValid(): |
| 151 | category = debugger.CreateCategory(category_name) |
| 152 | category.AddLanguage(lldb.eLanguageTypeC_plus_plus) |
| 153 | category.SetEnabled(True) |
| 154 | |
| 155 | def add_summary(typename, impl): |
| 156 | summary = None |
| 157 | |
| 158 | if isinstance(impl, str): |
| 159 | summary = lldb.SBTypeSummary.CreateWithSummaryString(impl) |
| 160 | summary.SetOptions(lldb.eTypeOptionCascade) |
| 161 | else: |
| 162 | # Unfortunately programmatic summary string generation is an entirely different codepath |
| 163 | # in LLDB. Register a convenient trampoline function which makes it look like it's part |
| 164 | # of the SyntheticChildrenProvider contract |
| 165 | summary = lldb.SBTypeSummary.CreateWithScriptCode(f''' |
| 166 | synth = {impl.__module__}.{impl.__qualname__}(valobj.GetNonSyntheticValue(), internal_dict) |
| 167 | synth.update() |
| 168 | |
| 169 | return synth.get_summary() |
| 170 | ''') |
| 171 | summary.SetOptions(lldb.eTypeOptionCascade | lldb.eTypeOptionFrontEndWantsDereference) |
| 172 | |
| 173 | category.AddTypeSummary(lldb.SBTypeNameSpecifier(typename, True), summary) |
| 174 | |
| 175 | def add_synthetic(typename, impl): |
| 176 | add_summary(typename, impl) |
| 177 | |
| 178 | synthetic = lldb.SBTypeSynthetic.CreateWithClassName(f"{impl.__module__}.{impl.__qualname__}") |
| 179 | synthetic.SetOptions(lldb.eTypeOptionCascade | lldb.eTypeOptionFrontEndWantsDereference) |
| 180 | |
| 181 | category.AddTypeSynthetic(lldb.SBTypeNameSpecifier(typename, True), synthetic) |
| 182 | |
| 183 | add_synthetic("^ImVector<.+>$", ImVectorSynth) |
| 184 | add_synthetic("^ImSpan<.+>$", ImSpanSynth) |
| 185 | |
| 186 | add_summary("^ImVec2$", "x=${var.x} y=${var.y}") |
| 187 | add_summary("^ImVec4$", "x=${var.x} y=${var.y} z=${var.z} w=${var.w}") |
| 188 | add_summary("^ImRect$", ImRectSummary) |
| 189 | add_summary("^ImGuiWindow$", ImGuiWindowSummary) |