diff --git a/tools/UEDataFormatter.py b/tools/UEDataFormatter.py index bea6c9a3..ee60c209 100644 --- a/tools/UEDataFormatter.py +++ b/tools/UEDataFormatter.py @@ -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: @@ -863,21 +952,40 @@ def TSharedPtrSummaryProvider(valobj, dict): # return 'expired' # return Object.Dereference().GetSummary() or ('0x%x' % addr) -def __lldb_init_module(debugger,dict): - global _FNameEntryType, _FNameEntryStride - _FNameEntryType = debugger.GetSelectedTarget().FindFirstType('FNameEntry') +def __lldb_init_module(debugger, dict): + 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