Procházet zdrojové kódy

Added type formatters for the LLDB debuggers (e.g. Xcode) (#8950)

David Mentler před 3 týdny
rodič
revize
087fbf08f6
2 změnil soubory, kde provedl 191 přidání a 0 odebrání
  1. 4 0
      misc/debuggers/README.txt
  2. 187 0
      misc/debuggers/imgui_lldb.py

+ 4 - 0
misc/debuggers/README.txt

@@ -14,3 +14,7 @@ imgui.natvis
     With this, types like ImVector<> will be displayed nicely in the debugger.
     (read comments inside file for details)
 
+imgui_lldb.py
+    LLDB: synthetic children provider and summaries for Dear ImGui types.
+    With this, types like ImVector<> will be displayed nicely in the debugger.
+    (read comments inside file for details)

+ 187 - 0
misc/debuggers/imgui_lldb.py

@@ -0,0 +1,187 @@
+# This file implements synthetic children providers and summaries for various ImGui types for LLDB. 
+#
+# 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 <path-to-this-file>
+#
+# 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)