Files
UnrealEngine/Engine/Extras/GDBPrinters/UEPrinters.py
2025-05-18 13:04:45 +08:00

738 lines
20 KiB
Python

#
# GDB Printers for the Unreal Engine 4
#
# How to install:
# If the file ~/.gdbinit doesn't exist
# touch ~/.gdbinit
# open ~/.gdbinit
#
# and add the following lines:
# python
# import sys
# ...
# sys.path.insert(0, '/Path/To/Epic/UE/Engine/Extras/GDBPrinters') <--
# ...
# from UEPrinters import register_ue_printers <--
# register_ue_printers (None) <--
# ...
# end
import itertools
import random
import re
import sys
import gdb
# ------------------------------------------------------------------------------
# We make our own base of the iterator to prevent issues between Python 2/3.
#
if sys.version_info[0] == 3:
Iterator = object
else:
class Iterator(object):
def next(self):
return type(self).__next__(self)
def default_iterator(val):
for field in val.type.fields():
yield field.name, val[field.name]
# ------------------------------------------------------------------------------
#
# Custom pretty printers.
#
#
# ------------------------------------------------------------------------------
# FBitReference
#
class FBitReferencePrinter:
def __init__(self, val):
self.Value = val
def to_string(self):
self.Mask = self.Value['Mask']
self.Data = self.Value['Data']
return '\'%d\'' % (self.Data & self.Mask)
# ------------------------------------------------------------------------------
# TBitArray
#
class TBitArrayPrinter:
"Print TBitArray"
class _iterator(Iterator):
def __init__(self, val):
self.Value = val
self.Counter = -1
try:
self.NumBits = self.Value['NumBits']
if self.NumBits.is_optimized_out:
self.NumBits = 0
else:
self.AllocatorInstance = self.Value['AllocatorInstance']
self.InlineData = self.AllocatorInstance['InlineData']
self.SecondaryData = self.AllocatorInstance['SecondaryData']
self.SecondaryDataData = None
if self.SecondaryData != None:
self.SecondaryDataData = self.SecondaryData['Data']
except:
raise
def __iter__(self):
return self
def __next__(self):
if self.NumBits == 0:
raise StopIteration
self.Counter = self.Counter + 1
if self.Counter >= self.NumBits:
raise StopIteration
if self.SecondaryDataData > 0:
data = self.SecondaryDataData.cast(gdb.lookup_type("uint32").pointer())
else:
data = self.InlineData.cast(gdb.lookup_type("uint32").pointer())
return ('[%d]' % self.Counter, (data[self.Counter/32] >> self.Counter) & 1)
def __init__(self, val):
self.Value = val
self.NumBits = self.Value['NumBits']
def to_string(self):
if self.NumBits.is_optimized_out:
pass
if self.NumBits == 0:
return 'empty'
pass
def children(self):
return self._iterator(self.Value)
def display_hint(self):
return 'array'
# ------------------------------------------------------------------------------
# TIndirectArray
#
# ------------------------------------------------------------------------------
# TChunkedArray
#
class TChunkedArrayPrinter:
"Print TChunkedArray"
class _iterator(Iterator):
def __init__(self, val):
self.Value = val
self.Counter = -1
self.ElementType = self.Value.type.template_argument(0)
self.ElementTypeSize = self.ElementType.sizeof
self.NumElementsPerChunk = self.Value.type.template_argument(1)/self.ElementTypeSize
try:
self.NumElements = self.Value['NumElements']
if self.NumElements.is_optimized_out:
self.NumElements = 0
else:
self.Chunks = self.Value['Chunks']
self.Array = self.Chunks['Array']
self.ArrayNum = self.Array['ArrayNum']
self.AllocatorInstance = self.Array['AllocatorInstance']
self.AllocatorData = self.AllocatorInstance['Data']
except:
raise
def __iter__(self):
return self
def __next__(self):
return self.next()
def __next__(self):
if self.NumElements == 0:
raise StopIteration
self.Counter = self.Counter + 1
if self.Counter >= self.NumElements:
raise StopIteration()
Expr = '*(*((('+str(self.ElementType.name)+'**)'+str(self.AllocatorData)+')+'+str(self.Counter / self.NumElementsPerChunk)+')+'+str(self.Counter % self.NumElementsPerChunk)+')'
Val = gdb.parse_and_eval(Expr)
return ('[%d]' % self.Counter, Val)
def __init__(self, val):
self.Value = val
self.NumElements = self.Value['NumElements']
def to_string(self):
if self.NumElements.is_optimized_out:
pass
if self.NumElements == 0:
return 'empty'
pass
def children(self):
return self._iterator(self.Value)
def display_hint(self):
return 'array'
# ------------------------------------------------------------------------------
# TSparseArray
#
class TSparseArrayPrinter:
"Print TSparseArray"
class _iterator(Iterator):
def __init__(self, val):
self.Value = val
self.Counter = -1
self.ElementType = self.Value.type.template_argument(0)
try:
self.NumFreeIndices = self.Value['NumFreeIndices']
self.Data = self.Value['Data']
self.InternalElementType = self.Data.type.template_argument(0)
self.ArrayNum = self.Data['ArrayNum']
if self.ArrayNum.is_optimized_out:
self.ArrayNum = 0
else:
self.AllocatorInstance = self.Data['AllocatorInstance']
self.AllocatorData = self.AllocatorInstance['Data']
self.AllocationFlags = self.Value['AllocationFlags']
self.AllocationFlagsInstance = self.AllocationFlags['AllocatorInstance']
self.InlineData = self.AllocationFlagsInstance['InlineData']
self.SecondaryData = self.AllocationFlagsInstance['SecondaryData']
self.SecondaryDataData = None
if self.SecondaryData != None:
self.SecondaryData['Data']
except:
raise
def __iter__(self):
return self
def __next__(self):
return self.next()
def __next__(self):
if self.ArrayNum == 0:
raise StopIteration
self.Counter = self.Counter + 1
if self.Counter >= self.ArrayNum:
raise StopIteration
else:
Data = None
if self.SecondaryDataData > 0:
Data = (self.SecondaryDataData.address.cast(gdb.lookup_type("int").pointer())[self.Counter/32] >> self.Counter) & 1
else:
Data = (self.InlineData.address.cast(gdb.lookup_type("int").pointer())[self.Counter/32] >> self.Counter) & 1
if Data != 0:
ElementOrFreeListValue = (self.AllocatorData.cast(self.InternalElementType.pointer()) + self.Counter).dereference()
Value = ElementOrFreeListValue['ElementData'].reinterpret_cast(self.ElementType.reference())
return ('[%d]' % self.Counter, Value)
else:
return self.__next__()
def __init__(self, val):
self.Value = val
self.ArrayNum = self.Value['Data']['ArrayNum']
def to_string(self):
if self.ArrayNum.is_optimized_out:
pass
if self.ArrayNum == 0:
return 'empty'
pass
def children(self):
return self._iterator(self.Value)
def display_hint(self):
return 'string'
# ------------------------------------------------------------------------------
# TSet
#
class TSetPrinter:
"Print TSet"
class _iterator(Iterator):
def __init__(self, val):
self.Value = val
self.Counter = -1
self.ElementType = self.Value.type.template_argument(0)
self.TSetElementType = gdb.lookup_type("TSetElement<" + self.ElementType.name + ">")
self.ElementsArrayNum = 0
try:
self.Elements = self.Value["Elements"]
self.ElementsData = self.Elements["Data"]
self.ElementsArrayNum = self.ElementsData['ArrayNum']
self.NumFreeIndices = self.Elements['NumFreeIndices']
self.AllocatorInstance = self.ElementsData['AllocatorInstance']
self.AllocationFlags = self.Elements['AllocationFlags']
self.AllocatorInstanceData = None
self.InlineData = None
self.SecondaryDataData = None
try:
self.AllocatorInstanceData = self.AllocatorInstance['Data']
except:
pass
try:
self.InlineData = self.AllocationFlags['AllocatorInstance']['InlineData']
except:
pass
try:
SecondaryData = self.AllocationFlags['AllocatorInstance']['SecondaryData']
if SecondaryData != None:
self.SecondaryDataData = SecondaryData['Data']
except:
pass
except:
self.ElementsArrayNum = 0
def __iter__(self):
return self
def __next__(self):
self.Counter = self.Counter + 1
if self.Counter >= self.ElementsArrayNum:
raise StopIteration()
else:
IsUsed = 0
if self.SecondaryDataData > 0:
IsUsed = (self.SecondaryDataData.address.cast(gdb.lookup_type("uint32").pointer())[self.Counter/32] >> self.Counter) & 1
else:
IsUsed = (self.InlineData.address.cast(gdb.lookup_type("uint32").pointer())[self.Counter/32] >> self.Counter) & 1
if IsUsed == 1:
Value = self.AllocatorInstanceData.cast(self.TSetElementType.pointer())[self.Counter]
return ('[%d]' % self.Counter, Value)
else:
return self.__next__()
def __init__(self, val):
self.Value = val
self.ArrayNum = self.Value["Elements"]["Data"]['ArrayNum']
def to_string(self):
if self.ArrayNum.is_optimized_out:
pass
if self.ArrayNum == 0:
return 'empty'
pass
def children(self):
return self._iterator(self.Value)
def display_hint(self):
return 'array'
# ------------------------------------------------------------------------------
# TSetElementPrinter
#
class TSetElementPrinter:
"Print TSetElement"
def __init__(self, val):
self.Value = val
def to_string(self):
if self.Value.is_optimized_out:
return '<optimized out>'
return self.Value["Value"]
def display_hint(self):
return "string"
# ------------------------------------------------------------------------------
# TMap
#
class TMapPrinter:
"Print TMap"
class _iterator(Iterator):
def __init__(self, val):
self.Value = val
self.Counter = -1
try:
self.Pairs = self.Value['Pairs']
if self.Pairs.is_optimized_out:
self.ArrayNum = 0
else:
self.Elements = self.Pairs['Elements']
self.ElementsData = self.Elements['Data']
self.ArrayNum = self.ElementsData['ArrayNum']
except:
raise
def __iter__(self):
return self
def __next__(self):
if self.ArrayNum == 0:
raise StopIteration
self.Counter = self.Counter + 1
if self.Counter > 0:
raise StopIteration
return ('Pairs', self.Pairs)
def __init__(self, val):
self.Value = val
self.ArrayNum = self.Value['Pairs']['Elements']['Data']['ArrayNum']
def children(self):
return self._iterator(self.Value)
def to_string(self):
if self.ArrayNum.is_optimized_out:
pass
if self.ArrayNum == 0:
return 'empty'
pass
def display_hint(self):
return 'map'
# ------------------------------------------------------------------------------
# TWeakObjectPtr
#
class TWeakObjectPtrPrinter:
"Print TWeakObjectPtr"
class _iterator(Iterator):
def __init__(self, val):
self.Value = val
self.Counter = 0
self.Object = None
self.ObjectSerialNumber = int(self.Value['ObjectSerialNumber'])
if self.ObjectSerialNumber >= 1:
ObjectIndexValue = int(self.Value['ObjectIndex'])
ObjectItemExpr = 'GCoreObjectArrayForDebugVisualizers->Objects['+str(ObjectIndexValue)+'/FChunkedFixedUObjectArray::NumElementsPerChunk]['+str(ObjectIndexValue)+ '% FChunkedFixedUObjectArray::NumElementsPerChunk]'
ObjectItem = gdb.parse_and_eval(ObjectItemExpr);
IsValidObject = int(ObjectItem['SerialNumber']) == self.ObjectSerialNumber
if IsValidObject == True:
ObjectType = self.Value.type.template_argument(0)
self.Object = ObjectItem['Object'].dereference().cast(ObjectType.reference())
def __iter__(self):
return self
def __next__(self):
if self.Counter > 0:
raise StopIteration
self.Counter = self.Counter + 1
if self.Object != None:
return ('Object', self.Object)
elif self.ObjectSerialNumber > 0:
return ('Object', 'STALE')
else:
return ('Object', 'nullptr')
def __init__(self, val):
self.Value = val
def children(self):
return self._iterator(self.Value)
def to_string(self):
ObjectType = self.Value.type.template_argument(0)
return 'TWeakObjectPtr<%s>' % ObjectType.name;
# ------------------------------------------------------------------------------
# FString
#
class FStringPrinter:
"Print FString"
def __init__(self, val):
self.Value = val
def to_string(self):
if self.Value.is_optimized_out:
return '<optimized out>'
ArrayNum = self.Value['Data']['ArrayNum']
if ArrayNum == 0:
return 'empty'
elif ArrayNum < 0:
return "nullptr"
else:
ActualData = self.Value['Data']['AllocatorInstance']['Data']
data = ActualData.cast(gdb.lookup_type("TCHAR").pointer())
return '%s' % (data.string())
def display_hint (self):
return 'string'
# ------------------------------------------------------------------------------
# FName shared
#
def lookup_fname_entry(id):
expr = '((FNameEntry&)GNameBlocksDebug[%d >> FNameDebugVisualizer::OffsetBits][FNameDebugVisualizer::EntryStride * (%d & FNameDebugVisualizer::OffsetMask)])' % (id, id)
return gdb.parse_and_eval(expr)
def get_fname_entry_string(entry):
header = entry['Header']
len = int(header['Len'].cast(gdb.lookup_type('uint16')))
is_wide = header['bIsWide'].cast(gdb.lookup_type('bool'))
if is_wide:
wide_string = entry['WideName'].cast(gdb.lookup_type('WIDECHAR').pointer())
return str(wide_string.string('','',len))
else:
ansi_string = entry['AnsiName'].cast(gdb.lookup_type('ANSICHAR').pointer())
return str(ansi_string.string('','',len))
def get_fname_string(entry, number):
if number == 0:
return "'%s'" % get_fname_entry_string(entry)
else:
return "'%s'_%u" % (get_fname_entry_string(entry), number - 1)
# ------------------------------------------------------------------------------
# FNameEntry
#
class FNameEntryPrinter:
"Print FNameEntry"
def __init__(self, val):
self.Value = val
def to_string(self):
header = self.Value['Header']
len = int(header['Len'].cast(gdb.lookup_type('uint16')))
if len == 0:
base_entry = lookup_fname_entry(int(self.Value['NumberedName']['Id']['Value']))
number = self.Value['NumberedName']['Number']
return get_fname_string(base_entry, number)
else:
return get_fname_string(self.Value, 0)
# ------------------------------------------------------------------------------
# FNameEntryId
#
class FNameEntryIdPrinter:
"Print FNameEntryId"
def __init__(self, val):
self.Value = val
def to_string(self):
if self.Value.is_optimized_out:
return '<optimized out>'
id = int(self.Value['Value'])
unused_mask = gdb.parse_and_eval('FNameDebugVisualizer::UnusedMask')
if (id & unused_mask) != 0:
return 'invalid'
return lookup_fname_entry(id)
# ------------------------------------------------------------------------------
# FName
#
class FNamePrinter:
"Print FName"
def __init__(self, id, number):
self.Id = id
self.Number = number
def to_string(self):
return get_fname_string(lookup_fname_entry(self.Id), self.Number)
def make_fname_printer(val):
if val.is_optimized_out:
return '<optimized out>'
id = int(val['ComparisonIndex']['Value'])
unused_mask = gdb.parse_and_eval('FNameDebugVisualizer::UnusedMask')
if (id & unused_mask) != 0:
return 'invalid'
# We need to pick the right printer based on whether FName has a Number member or not
if gdb.types.has_field(val.type, "Number"):
return FNamePrinter(id, int(val['Number']))
else:
# look up the id and use the FNameEntry printer
return FNameEntryPrinter(lookup_fname_entry(id))
# ------------------------------------------------------------------------------
# FMinimalName
#
def make_fminimalname_printer(val):
if val.is_optimized_out:
return '<optimized out>'
id = int(val['Index']['Value'])
unused_mask = gdb.parse_and_eval('FNameDebugVisualizer::UnusedMask')
if (id & unused_mask) != 0:
return 'invalid'
# We need to pick the right printer based on whether FName has a Number member or not
if gdb.types.has_field(val.type, "Number"):
return FNamePrinter(id, int(val['Number']))
else:
# look up the id and use the FNameEntry printer
return FNameEntryIdPrinter(val['Index'])
# ------------------------------------------------------------------------------
# TTuple
#
class TTuplePrinter:
"Print TTuple"
def __init__(self, val):
self.Value = val
try:
self.TKey = self.Value["Key"];
self.TValue = self.Value["Value"];
except:
pass
def to_string(self):
return '(%s, %s)' % (self.TKey, self.TValue)
# ------------------------------------------------------------------------------
# TArray
#
class TArrayPrinter:
"Print TArray"
class _iterator(Iterator):
def __init__(self, val):
self.Value = val
self.Counter = -1
self.TType = self.Value.type.template_argument(0)
try:
self.ArrayNum = self.Value['ArrayNum']
if self.ArrayNum.is_optimized_out:
self.ArrayNum = 0
if self.ArrayNum > 0:
self.AllocatorInstance = self.Value['AllocatorInstance']
self.AllocatorInstanceData = None
self.InlineData = None
self.SecondaryDataData = None
try:
self.AllocatorInstanceData = self.AllocatorInstance['Data']
except:
pass
try:
self.InlineData = self.AllocatorInstance['InlineData']
except:
pass
try:
SecondaryData = self.AllocatorInstance['SecondaryData']
if SecondaryData != None:
self.SecondaryDataData = SecondaryData['Data']
except:
pass
except:
raise
def __iter__(self):
return self
def __next__(self):
if self.ArrayNum == 0:
raise StopIteration
self.Counter = self.Counter + 1
if self.Counter >= self.ArrayNum:
raise StopIteration
try:
if self.AllocatorInstanceData != None:
data = self.AllocatorInstanceData.cast(self.TType.pointer())
elif self.SecondaryDataData != None:
data = self.SecondaryDataData.cast(self.TType.pointer())
else:
data = self.InlineData.cast(self.TType.pointer())
except:
return ('[%d]' % self.Counter, "optimized")
return ('[%d]' % self.Counter, data[self.Counter])
def __init__(self, val):
self.Value = val;
self.ArrayNum = self.Value['ArrayNum']
def to_string(self):
if self.ArrayNum.is_optimized_out:
pass
if self.ArrayNum == 0:
return 'empty'
pass
def children(self):
return self._iterator(self.Value)
def display_hint(self):
return 'array'
#
# Register our lookup function. If no objfile is passed use all globally.
def register_ue_printers(objfile):
if objfile == None:
objfile = gdb.current_objfile()
gdb.printing.register_pretty_printer(objfile, build_ue_pretty_printer(), True)
print("Registered pretty printers for UE classes")
def build_ue_pretty_printer():
# add a random numeric suffix to the printer name so we can reload printers during the same session for iteration
pp = gdb.printing.RegexpCollectionPrettyPrinter("UnrealEngine")
pp.add_printer("FString", '^FString$', FStringPrinter)
pp.add_printer("FNameEntry", '^FNameEntry$', FNameEntryPrinter)
pp.add_printer("FNameEntryId", '^FNameEntryId$', FNameEntryIdPrinter)
pp.add_printer("FName", '^FName$', make_fname_printer)
pp.add_printer("FMinimalName", '^FMinimalName$', make_fminimalname_printer)
pp.add_printer("TArray", '^TArray<.+,.+>$', TArrayPrinter)
pp.add_printer("TBitArray", '^TBitArray<.+>$', TBitArrayPrinter)
pp.add_printer("TChunkedArray", '^TChunkedArray<.+>$', TChunkedArrayPrinter)
pp.add_printer("TSparseArray", '^TSparseArray<.+>$', TSparseArrayPrinter)
pp.add_printer("TSetElement", '^TSetElement<.+>$', TSetElementPrinter)
pp.add_printer("TSet", '^TSet<.+>$', TSetPrinter)
pp.add_printer("FBitReference", '^FBitReference$', FBitReferencePrinter)
# pp.add_printer("TMap", '^TMap<.+,.+,.+>$', TMapPrinter)
pp.add_printer("TPair", '^TPair<.+,.+>$', TTuplePrinter)
pp.add_printer("TTuple", '^TTuple<.+,.+>$', TTuplePrinter)
pp.add_printer("TWeakObjectPtr", '^TWeakObjectPtr<.+>$', TWeakObjectPtrPrinter)
return pp
register_ue_printers(None)