Files
UnrealEngine/Engine/Extras/GDBPrinters/UEPrinters.py
Brandyn / Techy fcc1b09210 init
2026-04-04 15:40:51 -05:00

546 lines
15 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)
# ------------------------------------------------------------------------------
# Helper functions
#
def default_iterator(val):
for field in val.type.fields():
yield field.name, val[field.name]
def container_iterator(Array):
for i in range(Array.ArrayNum):
if Array.is_valid(i):
yield ('[%d]' % i, Array.get_value(i))
def get_allocation_data(Allocator, ElementType):
Data = None
try:
Data = Allocator['Data']
except:
pass
if (Data == None):
try:
SecondaryData = Allocator['SecondaryData']
if SecondaryData != None:
SecondaryDataData = SecondaryData['Data']
if (SecondaryDataData > 0):
Data = SecondaryDataData
except:
pass
if (Data == None):
try:
Data = Allocator['InlineData']
except:
pass
if (Data == None):
return 0
return Data.cast(ElementType.pointer())
# ------------------------------------------------------------------------------
#
# 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"
def __init__(self, val):
self.Value = val
self.ArrayNum = self.Value['NumBits']
self.ArrayMax = self.Value['MaxBits']
self.Data = get_allocation_data(self.Value['AllocatorInstance'], gdb.lookup_type("uint32"))
def to_string(self):
if self.ArrayNum.is_optimized_out:
pass
if self.ArrayNum == 0:
return 'empty'
return 'Num='+str(self.ArrayNum)+' Max='+str(self.ArrayMax)
def children(self):
return container_iterator(self)
def display_hint(self):
return 'array'
def get_value(self, index):
return ((self.Data[index/32] >> index) & 1) != 0
def is_valid(self, index):
return index < self.ArrayMax
# ------------------------------------------------------------------------------
# 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'
# ------------------------------------------------------------------------------
# TArray
#
class TArrayPrinter:
"Print TArray"
def __init__(self, val):
self.Value = val
self.ArrayNum = self.Value['ArrayNum']
self.ArrayMax = self.Value['ArrayMax']
self.Data = get_allocation_data(self.Value['AllocatorInstance'], self.Value.type.template_argument(0))
def to_string(self):
if self.ArrayNum.is_optimized_out:
pass
if self.ArrayNum == 0:
return 'empty'
return 'Num='+str(self.ArrayNum)+' Max='+str(self.ArrayMax)
def children(self):
return container_iterator(self)
def display_hint(self):
return 'array'
def get_value(self, index):
return self.Data[index]
def is_valid(self, index):
return index < self.ArrayMax
# ------------------------------------------------------------------------------
# TSparseArray
#
class TSparseArrayPrinter:
"Print TSparseArray"
def __init__(self, val):
self.Value = val
self.Array = TArrayPrinter(self.Value['Data'])
self.AllocationFlags = TBitArrayPrinter(self.Value['AllocationFlags'])
self.NumFreeIndices = self.Value['NumFreeIndices']
self.ArrayNum = self.Array.ArrayNum
self.ArrayMax = self.Array.ArrayMax
self.Data = self.Array.Data.cast(self.Value.type.template_argument(0).pointer())
def to_string(self):
if self.ArrayNum.is_optimized_out:
pass
if self.ArrayNum == 0:
return 'empty'
return 'Num='+str(self.ArrayNum - self.NumFreeIndices)+' Max='+str(self.ArrayMax)
def children(self):
return container_iterator(self)
def display_hint(self):
return 'array'
def get_value(self, index):
return self.Data[index]
def is_valid(self, index):
return self.AllocationFlags.get_value(index)
# ------------------------------------------------------------------------------
# TSet
#
class TSparseSetPrinter(TSparseArrayPrinter):
"Print TSparseSet"
def __init__(self, val):
super().__init__(val['Elements'])
def children(self):
for Str, Element in super().children():
yield (Str, Element['Value'])
# ------------------------------------------------------------------------------
# TSparseMap
#
class TSparseMapPrinter(TSparseSetPrinter):
"Print TSparseMap"
def __init__(self, val):
super().__init__(val['Pairs'])
def children(self):
for Str, Pair in super().children():
yield (f'[{Pair['Key']}] = {Pair['Value'].type}', Pair['Value'])
# ------------------------------------------------------------------------------
# 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'])
# ------------------------------------------------------------------------------
# FPrimaryAssetId
#
def remove_quotes(name):
return str(name).strip("'").strip('"')
class FPrimaryAssetIdPrinter:
"Print FPrimaryAssetId"
def __init__(self, val):
self.Value = val
def to_string(self):
return remove_quotes(self.Value["PrimaryAssetType"]["Name"]) + ':' + remove_quotes(self.Value["PrimaryAssetName"])
# ------------------------------------------------------------------------------
# TPair (TTuple<Key,Value>)
#
class TPairPrinter:
"Print TPair"
def __init__(self, val):
self.Value = val
def children(self):
yield ("Key", self.Value["Key"])
yield ("Value", self.Value["Value"])
#
# 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("TSet", '^TSet<.+>$', TSparseSetPrinter)
pp.add_printer("TSparseSet", '^TSparseSet<.+>$', TSparseSetPrinter)
pp.add_printer("FBitReference", '^FBitReference$', FBitReferencePrinter)
pp.add_printer("TMapBase", '^TMapBase<.+,.+,.+,.+>$', TSparseMapPrinter)
pp.add_printer("TSortableMapBase", '^TSortableMapBase<.+,.+,.+,.+>$', TSparseMapPrinter)
pp.add_printer("TMap", '^TMap<.+,.+,.+,.+>$', TSparseMapPrinter)
pp.add_printer("TSparseMapBase", '^TSparseMapBase<.+,.+,.+,.+>$', TSparseMapPrinter)
pp.add_printer("TSortableSparseMapBase", '^TSortableSparseMapBase<.+,.+,.+,.+>$', TSparseMapPrinter)
pp.add_printer("TSparseMap", '^TSparseMap<.+,.+,.+,.+>$', TSparseMapPrinter)
pp.add_printer("TPair", '^TPair<.+,.+>$', TPairPrinter)
pp.add_printer("TTuple", '^TTuple<.+,.+>$', TPairPrinter)
pp.add_printer("TWeakObjectPtr", '^TWeakObjectPtr<.+>$', TWeakObjectPtrPrinter)
pp.add_printer("FPrimaryAssetId", '^FPrimaryAssetId$', FPrimaryAssetIdPrinter)
return pp
register_ue_printers(None)