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.
# /*=============================================================================
# LLDB Data Formatters for Unreal Types
# =============================================================================*/
import lldb
import lldb, signal, sys, time
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
_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):
target = valobj.GetTarget()
@@ -53,6 +97,11 @@ def UEFStringSummaryProvider(valobj, dict):
if summary and summary.startswith('u"'): return summary[1:]
return summary
############################################################
#
# Provider for FName
#
############################################################
def UEFNameSummaryProvider(valobj, dict):
target = valobj.GetTarget()
@@ -64,8 +113,7 @@ def UEFNameSummaryProvider(valobj, dict):
block_idx = index >> 16
entry_offset = (index & 0xFFFF) * _FNameEntryStride
gname_var = target.FindFirstGlobalVariable('GNameBlocksDebug')
gname_ptr = gname_var.GetValueAsUnsigned(0)
gname_ptr = _GNameBlocksDebug.GetValueAsUnsigned(0)
error = lldb.SBError()
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)
return name
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.MightHaveChildren()
############################################################
#
# Providers for TObjectPtr
#
############################################################
class TObjectPtrSynthProvider(ForwardingSynthProvider):
def update(self):
@@ -124,115 +156,172 @@ class TObjectPtrSynthProvider(ForwardingSynthProvider):
self.forward_to = debug_ptr.Dereference()
def TObjectPtrSummaryProvider(valobj, dict):
debug_ptr = valobj.GetChildMemberWithName('DebugPtr')
debug_ptr = valobj.GetNonSyntheticValue().GetChildMemberWithName('DebugPtr')
addr = debug_ptr.GetValueAsUnsigned(0)
if addr == 0: return 'nullptr'
if addr & 1: return 'unresolved'
return '{...}'
############################################################
#
# Providers for TStrongObjectPtr
#
############################################################
class TSharedPtrSynthProvider:
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()
class TStrongObjectPtrSynthProvider(ForwardingSynthProvider):
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 TSharedPtrSummaryProvider(valobj, dict):
obj = valobj.GetChildMemberWithName('Object')
def TStrongObjectPtrSummaryProvider(valobj, dict):
obj = valobj.GetNonSyntheticValue().GetChildMemberWithName('Object')
addr = obj.GetValueAsUnsigned(0)
if addr == 0: return 'nullptr'
return '{...}'
############################################################
#
# Providers for TWeakObjectPtr
#
############################################################
# def UEUObjectBaseSummaryProvider(valobj,dict):
# Name = valobj.GetChildMemberWithName('NamePrivate')
# return Name.GetSummary()
def _resolve_weak_object_ptr(valobj):
obj_index = valobj.GetChildMemberWithName('ObjectIndex').GetValueAsSigned(0)
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):
# Name = valobj.GetChildMemberWithName('Name')
# return Name.GetSummary()
class TWeakObjectPtrSynthProvider(ForwardingSynthProvider):
def update(self):
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):
# Name = valobj.GetChildMemberWithName('NamePrivate')
# return Name.GetSummary()
def TWeakObjectPtrSummaryProvider(valobj, dict):
raw = valobj.GetNonSyntheticValue()
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):
# logger = lldb.formatters.Logger.Logger()
# self.valobj = valobj
class TSharedPtrSynthProvider(ForwardingSynthProvider):
def update(self):
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):
# logger = lldb.formatters.Logger.Logger()
# return 1
def TSharedPtrSummaryProvider(valobj, dict):
obj = valobj.GetNonSyntheticValue().GetChildMemberWithName('Object')
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):
# logger = lldb.formatters.Logger.Logger()
# logger >> "Retrieving child %s" % index
# if self.ObjectSerialNumberVal >= 1:
# Expr = 'GObjectArrayForDebugVisualizers->Objects[%s][%s].SerialNumber == %s' % (int(self.ObjectIndexVal/65536), self.ObjectIndexVal%65536, self.ObjectSerialNumberVal)
# Val = self.valobj.CreateValueFromExpression("%s" % self.ObjectIndexVal, Expr)
# Value = Val.GetValueAsUnsigned(0)
# if Value != 0:
# Expr = 'GObjectArrayForDebugVisualizers->Objects[%s][%s].Object' % (int(self.ObjectIndexVal/65536), self.ObjectIndexVal%65536)
# return self.valobj.CreateValueFromExpression('Object', Expr)
# else:
# Expr = '(void*)0xDEADBEEF'
# return self.valobj.CreateValueFromExpression('Object', Expr)
def _resolve_weak_ptr(valobj):
"""Returns (object SBValue, is_expired).
valobj must be the raw (non-synthetic) TWeakPtr."""
weak_ref = valobj.GetChildMemberWithName('WeakReferenceCount')
ref_ctrl_ptr_val = weak_ref.GetChildMemberWithName('ReferenceController')
if ref_ctrl_ptr_val.GetValueAsUnsigned(0) == 0: return None, False
shared_count_field = ref_ctrl_ptr_val.Dereference().GetChildMemberWithName('SharedReferenceCount')
if shared_count_field.GetValueAsSigned(0) <= 0: return None, True
obj = valobj.GetChildMemberWithName('Object')
return obj, False
# Expr = 'nullptr'
# return self.valobj.CreateValueFromExpression('Object', Expr)
class TWeakPtrSynthProvider(ForwardingSynthProvider):
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):
# logger = lldb.formatters.Logger.Logger()
# try:
# self.ObjectSerialNumber = self.valobj.GetChildMemberWithName('ObjectSerialNumber')
# self.ObjectSerialNumberVal = self.ObjectSerialNumber.GetValueAsSigned(0)
# self.ObjectIndex = self.valobj.GetChildMemberWithName('ObjectIndex')
# self.ObjectIndexVal = self.ObjectIndex.GetValueAsSigned(0)
# except:
# pass
def TWeakPtrSummaryProvider(valobj, dict):
obj, expired = _resolve_weak_ptr(valobj.GetNonSyntheticValue())
if expired: return 'expired'
if obj is None: return 'nullptr'
return '{...}'
# def has_children(self):
# return True
############################################################
#
# Providers for TUniquePtr
#
############################################################
# def UEFWeakObjectPtrSummaryProvider(valobj,dict):
# ObjectSerialNumber = valobj.GetChildMemberWithName('ObjectSerialNumber')
# ObjectSerialNumberVal = ObjectSerialNumber.GetValueAsSigned(0)
# if ObjectSerialNumberVal < 1:
# return 'object=nullptr'
# ObjectIndex = valobj.GetChildMemberWithName('ObjectIndex')
# ObjectIndexVal = ObjectIndex.GetValueAsSigned(0)
# Expr = 'GObjectArrayForDebugVisualizers->Objects[%s][%s].SerialNumber == %s' % (int(ObjectIndexVal/65536), ObjectIndexVal%65536, ObjectSerialNumberVal)
# Val = valobj.CreateValueFromExpression('%s' % ObjectIndexVal, Expr)
# ValRef = Val.GetValueAsUnsigned(0)
# if ValRef == 0:
# return 'object=STALE'
# else:
# Expr = 'GObjectArrayForDebugVisualizers->Objects[%s][%s].Object' % (int(ObjectIndexVal/65536), ObjectIndexVal%65536)
# Val = valobj.CreateValueFromExpression("%s" % ObjectIndexVal, Expr)
# return 'object=' + Val.GetValue()
class TUniquePtrSynthProvider(ForwardingSynthProvider):
def update(self):
ptr = self.valobj.GetChildMemberWithName('Ptr')
addr = ptr.GetValueAsUnsigned(0)
if addr == 0:
self.forward_to = lldb.SBValue()
else:
self.forward_to = ptr.Dereference()
def TUniquePtrSummaryProvider(valobj, dict):
ptr = valobj.GetNonSyntheticValue().GetChildMemberWithName('Ptr')
addr = ptr.GetValueAsUnsigned(0)
if addr == 0: return 'nullptr'
return '{...}'
############################################################
#
# 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:
@@ -864,20 +953,39 @@ def TSharedPtrSummaryProvider(valobj, dict):
# return Object.Dereference().GetSummary() or ('0x%x' % addr)
def __lldb_init_module(debugger, dict):
global _FNameEntryType, _FNameEntryStride
_FNameEntryType = debugger.GetSelectedTarget().FindFirstType('FNameEntry')
print("Running lldb_init_module")
debugger.HandleCommand('type category delete UEDataFormatters')
global _FNameEntryType, _FNameEntryStride, _FUObjectItemType
global _GNameBlocksDebug, _GObjectArrayForDebugVisualizers
target = debugger.GetSelectedTarget()
_FNameEntryType = target.FindFirstType('FNameEntry')
_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.UEFNameSummaryProvider -e FName -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 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 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 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 UObjectBaseUtility -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.UEFWeakObjectPtrSummaryProvider -e FWeakObjectPtr -w UEDataFormatters')
@@ -938,6 +1046,13 @@ def _patch_codelldb_value():
_patch_codelldb_value()
##############################################################
#
# FV
#
#############################################################
def fv(expr):
"""Evaluate a Python-syntax expression against the current frame's variables.
@@ -984,10 +1099,13 @@ def FindNamedChild(value, name):
return lldb.SBValue()
##############################################################
#
# 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):
n = self.GetNumChildren()
if n == 0: return _sbvalue_display(self)
@@ -1008,6 +1126,3 @@ def _sbvalue_display(v):
lldb.SBValue.__repr__ = _sbvalue_repr
import builtins
builtins.fv = fv
builtins.fv2 = fv2