More work on getting lldb data formatters in better shape.

This commit is contained in:
2026-04-20 08:23:36 -04:00
parent 21d8c40005
commit f3e1daf4fe

View File

@@ -1,34 +1,78 @@
# Copyright Epic Games, Inc. All Rights Reserved.
# /*============================================================================= import lldb, signal, sys, time
# LLDB Data Formatters for Unreal Types
# =============================================================================*/
import lldb
import lldb.formatters.Logger import lldb.formatters.Logger
import faulthandler
import signal
import sys
import time
# Uncomment the line below to have the data formatters emit debug logging
# lldb.formatters.Logger._lldb_formatters_debug_level=1
# What documentation there is for parsing values in LLDB can be found here:
# https://lldb.llvm.org/python_reference/index.html
# https://lldb.llvm.org/python_reference/lldb.SBValue-class.html
# To install:
# 1) Open Terminal and run:
# touch ~/.lldbinit
# open ~/.lldbinit
# 2) Add the following text to .lldbini and save - modifying the path as appropriate:
# settings set target.inline-breakpoint-strategy always
# command script import "/Path/To/Epic/UE/Engine/Extras/LLDBDataFormatters/UEDataFormatter.py"
# 3) Restart Xcode
_FNameEntryType = None _FNameEntryType = None
_FNameEntryStride = 2 _FNameEntryStride = None
_FUObjectItemType = None
_GNameBlocksDebug = None
_GObjectArrayForDebugVisualizers = None
############################################################
#
# A forwarding synth provider is a provider that
# forwards all child lookups to a different SBValue.
#
############################################################
class ForwardingSynthProvider:
def __init__(self, valobj, dict):
self.valobj = valobj
self.update()
def num_children(self):
return self.forward_to.GetNumChildren()
def get_child_index(self, name):
return self.forward_to.GetIndexOfChildWithName(name)
def get_child_at_index(self, index):
return self.forward_to.GetChildAtIndex(index)
def update(self):
self.forward_to = lldb.SBValue()
def has_children(self):
return self.forward_to.IsValid() and self.forward_to.MightHaveChildren()
############################################################
#
# A Stored synth provider is an synth provider that
# just stores an array of children.
#
############################################################
class StoredSynthProvider:
def __init__(self, valobj, dict):
self.valobj = valobj
self.update()
def num_children(self):
return len(self.children)
def get_child_index(self, name):
for i, child in enumerate(self.children):
if child.GetName() == name:
return i
return -1
def get_child_at_index(self, index):
if 0 <= index < len(self.children):
return self.children[index]
return lldb.SBValue()
def update(self):
self.children = []
def has_children(self):
return len(self.children) > 0
############################################################
#
# Provider for FString
#
############################################################
def UEFStringSummaryProvider(valobj, dict): def UEFStringSummaryProvider(valobj, dict):
target = valobj.GetTarget() target = valobj.GetTarget()
@@ -53,6 +97,11 @@ def UEFStringSummaryProvider(valobj, dict):
if summary and summary.startswith('u"'): return summary[1:] if summary and summary.startswith('u"'): return summary[1:]
return summary return summary
############################################################
#
# Provider for FName
#
############################################################
def UEFNameSummaryProvider(valobj, dict): def UEFNameSummaryProvider(valobj, dict):
target = valobj.GetTarget() target = valobj.GetTarget()
@@ -64,8 +113,7 @@ def UEFNameSummaryProvider(valobj, dict):
block_idx = index >> 16 block_idx = index >> 16
entry_offset = (index & 0xFFFF) * _FNameEntryStride entry_offset = (index & 0xFFFF) * _FNameEntryStride
gname_var = target.FindFirstGlobalVariable('GNameBlocksDebug') gname_ptr = _GNameBlocksDebug.GetValueAsUnsigned(0)
gname_ptr = gname_var.GetValueAsUnsigned(0)
error = lldb.SBError() error = lldb.SBError()
block_addr = process.ReadPointerFromMemory(gname_ptr + block_idx * target.GetAddressByteSize(), error) block_addr = process.ReadPointerFromMemory(gname_ptr + block_idx * target.GetAddressByteSize(), error)
@@ -92,27 +140,11 @@ def UEFNameSummaryProvider(valobj, dict):
if number > 0: return '%s_%d' % (name, number - 1) if number > 0: return '%s_%d' % (name, number - 1)
return name return name
############################################################
class ForwardingSynthProvider: #
def __init__(self, valobj, dict): # Providers for TObjectPtr
self.valobj = valobj #
self.update() ############################################################
def num_children(self):
return self.forward_to.GetNumChildren()
def get_child_index(self, name):
return self.forward_to.GetIndexOfChildWithName(name)
def get_child_at_index(self, index):
return self.forward_to.GetChildAtIndex(index)
def update(self):
self.forward_to = lldb.SBValue()
def has_children(self):
return self.forward_to.MightHaveChildren()
class TObjectPtrSynthProvider(ForwardingSynthProvider): class TObjectPtrSynthProvider(ForwardingSynthProvider):
def update(self): def update(self):
@@ -124,115 +156,172 @@ class TObjectPtrSynthProvider(ForwardingSynthProvider):
self.forward_to = debug_ptr.Dereference() self.forward_to = debug_ptr.Dereference()
def TObjectPtrSummaryProvider(valobj, dict): def TObjectPtrSummaryProvider(valobj, dict):
debug_ptr = valobj.GetChildMemberWithName('DebugPtr') debug_ptr = valobj.GetNonSyntheticValue().GetChildMemberWithName('DebugPtr')
addr = debug_ptr.GetValueAsUnsigned(0) addr = debug_ptr.GetValueAsUnsigned(0)
if addr == 0: return 'nullptr' if addr == 0: return 'nullptr'
if addr & 1: return 'unresolved' if addr & 1: return 'unresolved'
return '{...}' return '{...}'
############################################################
#
# Providers for TStrongObjectPtr
#
############################################################
class TSharedPtrSynthProvider: class TStrongObjectPtrSynthProvider(ForwardingSynthProvider):
def __init__(self, valobj, dict):
self.valobj = valobj
self.update()
def num_children(self):
return 1
def has_children(self):
return true
def get_child_index(self, name):
if name == 'Object': return 0
return -1
def get_child_at_index(self, index):
if index == 0: return self.object
return lldb.SBValue()
def update(self): def update(self):
self.object = self.valobj.GetChildMemberWithName('Object') obj = self.valobj.GetChildMemberWithName('Object')
addr = obj.GetValueAsUnsigned(0)
if addr == 0:
self.forward_to = lldb.SBValue()
else:
self.forward_to = obj.Dereference()
def TStrongObjectPtrSummaryProvider(valobj, dict):
def TSharedPtrSummaryProvider(valobj, dict): obj = valobj.GetNonSyntheticValue().GetChildMemberWithName('Object')
obj = valobj.GetChildMemberWithName('Object')
addr = obj.GetValueAsUnsigned(0) addr = obj.GetValueAsUnsigned(0)
if addr == 0: return 'nullptr' if addr == 0: return 'nullptr'
return '{...}' return '{...}'
############################################################
#
# Providers for TWeakObjectPtr
#
############################################################
# def UEUObjectBaseSummaryProvider(valobj,dict): def _resolve_weak_object_ptr(valobj):
# Name = valobj.GetChildMemberWithName('NamePrivate') obj_index = valobj.GetChildMemberWithName('ObjectIndex').GetValueAsSigned(0)
# return Name.GetSummary() serial_num = valobj.GetChildMemberWithName('ObjectSerialNumber').GetValueAsSigned(0)
if serial_num == 0 or obj_index < 0: return None
chunk_idx = obj_index >> 16 # NumElementsPerChunk = 65536
item_idx = obj_index & 0xFFFF
objects = _GObjectArrayForDebugVisualizers.Dereference().GetChildMemberWithName('Objects') # FUObjectItem**
chunk = objects.GetChildAtIndex(chunk_idx, lldb.eDynamicDontRunTarget, True) # FUObjectItem*
item = chunk.GetChildAtIndex(item_idx, lldb.eDynamicDontRunTarget, True) # FUObjectItem
actual_serial = item.GetChildMemberWithName('SerialNumber').GetValueAsSigned(0)
if actual_serial != serial_num: return None
return item.GetChildMemberWithName('Object')
# def UEFFieldClassSummaryProvider(valobj,dict): class TWeakObjectPtrSynthProvider(ForwardingSynthProvider):
# Name = valobj.GetChildMemberWithName('Name') def update(self):
# return Name.GetSummary() obj_ptr = _resolve_weak_object_ptr(self.valobj)
if obj_ptr is None or obj_ptr.GetValueAsUnsigned(0) == 0:
self.forward_to = lldb.SBValue()
else:
self.forward_to = obj_ptr.Dereference()
# def UEFFieldSummaryProvider(valobj,dict): def TWeakObjectPtrSummaryProvider(valobj, dict):
# Name = valobj.GetChildMemberWithName('NamePrivate') raw = valobj.GetNonSyntheticValue()
# return Name.GetSummary() serial_num = raw.GetChildMemberWithName('ObjectSerialNumber').GetValueAsSigned(0)
if serial_num == 0: return 'nullptr'
if _resolve_weak_object_ptr(raw) is None: return 'expired'
return '{...}'
# class UETWeakObjectPtrSynthProvider: ############################################################
#
# Providers for TSharedPtr
#
############################################################
# def __init__(self, valobj, dict): class TSharedPtrSynthProvider(ForwardingSynthProvider):
# logger = lldb.formatters.Logger.Logger() def update(self):
# self.valobj = valobj obj = self.valobj.GetChildMemberWithName('Object')
addr = obj.GetValueAsUnsigned(0)
if addr == 0:
self.forward_to = lldb.SBValue()
else:
self.forward_to = obj.Dereference()
# def num_children(self): def TSharedPtrSummaryProvider(valobj, dict):
# logger = lldb.formatters.Logger.Logger() obj = valobj.GetNonSyntheticValue().GetChildMemberWithName('Object')
# return 1 addr = obj.GetValueAsUnsigned(0)
if addr == 0: return 'nullptr'
return '{...}'
# def get_child_index(self,name): ############################################################
# logger = lldb.formatters.Logger.Logger() #
# return 0 # Providers for TWeakPtr
#
############################################################
# def get_child_at_index(self,index): def _resolve_weak_ptr(valobj):
# logger = lldb.formatters.Logger.Logger() """Returns (object SBValue, is_expired).
# logger >> "Retrieving child %s" % index valobj must be the raw (non-synthetic) TWeakPtr."""
# if self.ObjectSerialNumberVal >= 1: weak_ref = valobj.GetChildMemberWithName('WeakReferenceCount')
# Expr = 'GObjectArrayForDebugVisualizers->Objects[%s][%s].SerialNumber == %s' % (int(self.ObjectIndexVal/65536), self.ObjectIndexVal%65536, self.ObjectSerialNumberVal) ref_ctrl_ptr_val = weak_ref.GetChildMemberWithName('ReferenceController')
# Val = self.valobj.CreateValueFromExpression("%s" % self.ObjectIndexVal, Expr) if ref_ctrl_ptr_val.GetValueAsUnsigned(0) == 0: return None, False
# Value = Val.GetValueAsUnsigned(0) shared_count_field = ref_ctrl_ptr_val.Dereference().GetChildMemberWithName('SharedReferenceCount')
# if Value != 0: if shared_count_field.GetValueAsSigned(0) <= 0: return None, True
# Expr = 'GObjectArrayForDebugVisualizers->Objects[%s][%s].Object' % (int(self.ObjectIndexVal/65536), self.ObjectIndexVal%65536) obj = valobj.GetChildMemberWithName('Object')
# return self.valobj.CreateValueFromExpression('Object', Expr) return obj, False
# else:
# Expr = '(void*)0xDEADBEEF'
# return self.valobj.CreateValueFromExpression('Object', Expr)
# Expr = 'nullptr' class TWeakPtrSynthProvider(ForwardingSynthProvider):
# return self.valobj.CreateValueFromExpression('Object', Expr) def update(self):
obj, expired = _resolve_weak_ptr(self.valobj)
if obj is None or obj.GetValueAsUnsigned(0) == 0:
self.forward_to = lldb.SBValue()
else:
self.forward_to = obj.Dereference()
# def update(self): def TWeakPtrSummaryProvider(valobj, dict):
# logger = lldb.formatters.Logger.Logger() obj, expired = _resolve_weak_ptr(valobj.GetNonSyntheticValue())
# try: if expired: return 'expired'
# self.ObjectSerialNumber = self.valobj.GetChildMemberWithName('ObjectSerialNumber') if obj is None: return 'nullptr'
# self.ObjectSerialNumberVal = self.ObjectSerialNumber.GetValueAsSigned(0) return '{...}'
# self.ObjectIndex = self.valobj.GetChildMemberWithName('ObjectIndex')
# self.ObjectIndexVal = self.ObjectIndex.GetValueAsSigned(0)
# except:
# pass
# def has_children(self): ############################################################
# return True #
# Providers for TUniquePtr
#
############################################################
# def UEFWeakObjectPtrSummaryProvider(valobj,dict): class TUniquePtrSynthProvider(ForwardingSynthProvider):
# ObjectSerialNumber = valobj.GetChildMemberWithName('ObjectSerialNumber') def update(self):
# ObjectSerialNumberVal = ObjectSerialNumber.GetValueAsSigned(0) ptr = self.valobj.GetChildMemberWithName('Ptr')
# if ObjectSerialNumberVal < 1: addr = ptr.GetValueAsUnsigned(0)
# return 'object=nullptr' if addr == 0:
# ObjectIndex = valobj.GetChildMemberWithName('ObjectIndex') self.forward_to = lldb.SBValue()
# ObjectIndexVal = ObjectIndex.GetValueAsSigned(0) else:
# Expr = 'GObjectArrayForDebugVisualizers->Objects[%s][%s].SerialNumber == %s' % (int(ObjectIndexVal/65536), ObjectIndexVal%65536, ObjectSerialNumberVal) self.forward_to = ptr.Dereference()
# Val = valobj.CreateValueFromExpression('%s' % ObjectIndexVal, Expr)
# ValRef = Val.GetValueAsUnsigned(0) def TUniquePtrSummaryProvider(valobj, dict):
# if ValRef == 0: ptr = valobj.GetNonSyntheticValue().GetChildMemberWithName('Ptr')
# return 'object=STALE' addr = ptr.GetValueAsUnsigned(0)
# else: if addr == 0: return 'nullptr'
# Expr = 'GObjectArrayForDebugVisualizers->Objects[%s][%s].Object' % (int(ObjectIndexVal/65536), ObjectIndexVal%65536) return '{...}'
# Val = valobj.CreateValueFromExpression("%s" % ObjectIndexVal, Expr)
# return 'object=' + Val.GetValue() ############################################################
#
# Providers for UObject
#
############################################################
class UObjectSynthProvider(StoredSynthProvider):
def update(self):
self.children = []
raw = self.valobj
target = raw.GetTarget()
debug_ptr = raw.GetChildMemberWithName('ClassPrivate') \
.GetChildMemberWithName('ObjectPtr') \
.GetChildMemberWithName('DebugPtr')
class_val = target.CreateValueFromAddress(
'Class', lldb.SBAddress(debug_ptr.GetLoadAddress(), target), debug_ptr.GetType())
self.children.append(class_val)
name_field = raw.GetChildMemberWithName('NamePrivate')
name_val = target.CreateValueFromAddress(
'Name', lldb.SBAddress(name_field.GetLoadAddress(), target), name_field.GetType())
self.children.append(name_val)
def UObjectSummaryProvider(valobj, dict):
raw = valobj.GetNonSyntheticValue()
class_debug_ptr = raw.GetChildMemberWithName('ClassPrivate') \
.GetChildMemberWithName('ObjectPtr') \
.GetChildMemberWithName('DebugPtr')
class_addr = class_debug_ptr.GetValueAsUnsigned(0)
if not class_addr:
return 'null'
return UEFNameSummaryProvider(
class_debug_ptr.Dereference().GetChildMemberWithName('NamePrivate'), dict)
# class UEChunkedArraySynthProvider: # class UEChunkedArraySynthProvider:
@@ -863,21 +952,40 @@ def TSharedPtrSummaryProvider(valobj, dict):
# return 'expired' # return 'expired'
# return Object.Dereference().GetSummary() or ('0x%x' % addr) # return Object.Dereference().GetSummary() or ('0x%x' % addr)
def __lldb_init_module(debugger,dict): def __lldb_init_module(debugger, dict):
global _FNameEntryType, _FNameEntryStride print("Running lldb_init_module")
_FNameEntryType = debugger.GetSelectedTarget().FindFirstType('FNameEntry') debugger.HandleCommand('type category delete UEDataFormatters')
global _FNameEntryType, _FNameEntryStride, _FUObjectItemType
global _GNameBlocksDebug, _GObjectArrayForDebugVisualizers
target = debugger.GetSelectedTarget()
_FNameEntryType = target.FindFirstType('FNameEntry')
_FNameEntryStride = _FNameEntryType.GetByteAlign() _FNameEntryStride = _FNameEntryType.GetByteAlign()
_FUObjectItemType = target.FindFirstType('FUObjectItem')
_GNameBlocksDebug = target.FindFirstGlobalVariable('GNameBlocksDebug')
if not _GNameBlocksDebug.IsValid():
print('UEDataFormatter: GNameBlocksDebug not found')
_GObjectArrayForDebugVisualizers = target.FindFirstGlobalVariable('GObjectArrayForDebugVisualizers')
if not _GObjectArrayForDebugVisualizers.IsValid():
print('UEDataFormatter: GObjectArrayForDebugVisualizers not found')
debugger.HandleCommand('type summary add -F UEDataFormatter.UEFStringSummaryProvider -e FString -w UEDataFormatters') debugger.HandleCommand('type summary add -F UEDataFormatter.UEFStringSummaryProvider -e FString -w UEDataFormatters')
debugger.HandleCommand('type summary add -F UEDataFormatter.UEFNameSummaryProvider -e FName -w UEDataFormatters') debugger.HandleCommand('type summary add -F UEDataFormatter.UEFNameSummaryProvider -e FName -w UEDataFormatters')
debugger.HandleCommand('type synthetic add -l UEDataFormatter.TObjectPtrSynthProvider -x "^TObjectPtr<.+>$" -w UEDataFormatters') debugger.HandleCommand('type synthetic add -l UEDataFormatter.TObjectPtrSynthProvider -x "^TObjectPtr<.+>$" -w UEDataFormatters')
debugger.HandleCommand('type summary add -F UEDataFormatter.TObjectPtrSummaryProvider -e -x "^TObjectPtr<.+>$" -w UEDataFormatters') debugger.HandleCommand('type summary add -F UEDataFormatter.TObjectPtrSummaryProvider -e -x "^TObjectPtr<.+>$" -w UEDataFormatters')
debugger.HandleCommand('type synthetic add -l UEDataFormatter.TStrongObjectPtrSynthProvider -x "^TStrongObjectPtr<.+>$" -w UEDataFormatters')
debugger.HandleCommand('type summary add -F UEDataFormatter.TStrongObjectPtrSummaryProvider -e -x "^TStrongObjectPtr<.+>$" -w UEDataFormatters')
debugger.HandleCommand('type synthetic add -l UEDataFormatter.TSharedPtrSynthProvider -x "^TSharedPtr<.+>$" -w UEDataFormatters') debugger.HandleCommand('type synthetic add -l UEDataFormatter.TSharedPtrSynthProvider -x "^TSharedPtr<.+>$" -w UEDataFormatters')
debugger.HandleCommand('type summary add -F UEDataFormatter.TSharedPtrSummaryProvider -e -x "^TSharedPtr<.+>$" -w UEDataFormatters') debugger.HandleCommand('type summary add -F UEDataFormatter.TSharedPtrSummaryProvider -e -x "^TSharedPtr<.+>$" -w UEDataFormatters')
debugger.HandleCommand('type synthetic add -l UEDataFormatter.TSharedPtrSynthProvider -x "^TSharedRef<.+>$" -w UEDataFormatters') debugger.HandleCommand('type synthetic add -l UEDataFormatter.TSharedPtrSynthProvider -x "^TSharedRef<.+>$" -w UEDataFormatters')
debugger.HandleCommand('type summary add -F UEDataFormatter.TSharedPtrSummaryProvider -e -x "^TSharedRef<.+>$" -w UEDataFormatters') debugger.HandleCommand('type summary add -F UEDataFormatter.TSharedPtrSummaryProvider -e -x "^TSharedRef<.+>$" -w UEDataFormatters')
# debugger.HandleCommand('type summary add -F UEDataFormatter.UEUObjectBaseSummaryProvider -e UObject -w UEDataFormatters') debugger.HandleCommand('type synthetic add -l UEDataFormatter.TWeakPtrSynthProvider -x "^TWeakPtr<.+>$" -w UEDataFormatters')
debugger.HandleCommand('type summary add -F UEDataFormatter.TWeakPtrSummaryProvider -e -x "^TWeakPtr<.+>$" -w UEDataFormatters')
debugger.HandleCommand('type synthetic add -l UEDataFormatter.TWeakObjectPtrSynthProvider -x "^TWeakObjectPtr<.+>$" -w UEDataFormatters')
debugger.HandleCommand('type summary add -F UEDataFormatter.TWeakObjectPtrSummaryProvider -e -x "^TWeakObjectPtr<.+>$" -w UEDataFormatters')
debugger.HandleCommand('type synthetic add -l UEDataFormatter.TWeakObjectPtrSynthProvider -x "^FWeakObjectPtr$" -w UEDataFormatters')
debugger.HandleCommand('type summary add -F UEDataFormatter.TWeakObjectPtrSummaryProvider -e FWeakObjectPtr -w UEDataFormatters')
debugger.HandleCommand('type synthetic add -l UEDataFormatter.UObjectSynthProvider UObject -w UEDataFormatters')
debugger.HandleCommand('type summary add -F UEDataFormatter.UObjectSummaryProvider -e UObject -w UEDataFormatters')
# debugger.HandleCommand('type summary add -F UEDataFormatter.UEUObjectBaseSummaryProvider -e UObjectBase -w UEDataFormatters') # debugger.HandleCommand('type summary add -F UEDataFormatter.UEUObjectBaseSummaryProvider -e UObjectBase -w UEDataFormatters')
# debugger.HandleCommand('type summary add -F UEDataFormatter.UEUObjectBaseSummaryProvider -e UObjectBaseUtility -w UEDataFormatters')
# debugger.HandleCommand('type summary add -F UEDataFormatter.UEFFieldClassSummaryProvider -e FFieldClass -w UEDataFormatters') # debugger.HandleCommand('type summary add -F UEDataFormatter.UEFFieldClassSummaryProvider -e FFieldClass -w UEDataFormatters')
# debugger.HandleCommand('type summary add -F UEDataFormatter.UEFFieldSummaryProvider -e FField -w UEDataFormatters') # debugger.HandleCommand('type summary add -F UEDataFormatter.UEFFieldSummaryProvider -e FField -w UEDataFormatters')
# debugger.HandleCommand('type summary add -F UEDataFormatter.UEFWeakObjectPtrSummaryProvider -e FWeakObjectPtr -w UEDataFormatters') # debugger.HandleCommand('type summary add -F UEDataFormatter.UEFWeakObjectPtrSummaryProvider -e FWeakObjectPtr -w UEDataFormatters')
@@ -938,6 +1046,13 @@ def _patch_codelldb_value():
_patch_codelldb_value() _patch_codelldb_value()
##############################################################
#
# FV
#
#############################################################
def fv(expr): def fv(expr):
"""Evaluate a Python-syntax expression against the current frame's variables. """Evaluate a Python-syntax expression against the current frame's variables.
@@ -984,10 +1099,13 @@ def FindNamedChild(value, name):
return lldb.SBValue() return lldb.SBValue()
##############################################################
# #
# The standard repr for SBValue will recurse infinitely if you # The standard repr for SBValue will recurse infinitely if you
# provide useful synth providers for smart pointers. This won't. # provide useful synth providers for smart pointers
# #
##############################################################
def _sbvalue_repr(self): def _sbvalue_repr(self):
n = self.GetNumChildren() n = self.GetNumChildren()
if n == 0: return _sbvalue_display(self) if n == 0: return _sbvalue_display(self)
@@ -1008,6 +1126,3 @@ def _sbvalue_display(v):
lldb.SBValue.__repr__ = _sbvalue_repr lldb.SBValue.__repr__ = _sbvalue_repr
import builtins
builtins.fv = fv
builtins.fv2 = fv2