diff options
Diffstat (limited to 'misc/scripts/godot_gdb_pretty_print.py')
-rwxr-xr-x | misc/scripts/godot_gdb_pretty_print.py | 116 |
1 files changed, 116 insertions, 0 deletions
diff --git a/misc/scripts/godot_gdb_pretty_print.py b/misc/scripts/godot_gdb_pretty_print.py new file mode 100755 index 0000000000..932831d24e --- /dev/null +++ b/misc/scripts/godot_gdb_pretty_print.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python3 +# Load this file to your GDB session to enable pretty-printing +# of some Godot C++ types. +# GDB command: source misc/scripts/godot_gdb_pretty_print.py +# +# To load these automatically in Visual Studio Code, +# add the source command to the setupCommands of your configuration +# in launch.json. +# "setupCommands": [ +# ... +# { +# "description": "Load custom pretty-printers for Godot types.", +# "text": "source ${workspaceRoot}/misc/scripts/godot_gdb_pretty_print.py" +# } +# ] +# Other UI:s that use GDB under the hood are likely to have their own ways to achieve this. +# +# To debug this script it's easiest to use the interactive python from a command-line +# GDB session. Stop at a breakpoint, then use +# python-interactive to enter the python shell and +# acquire a Value object using gdb.selected_frame().read_var("variable name"). +# From there you can figure out how to print it nicely. +import re + +import gdb + + +# Printer for Godot StringName variables. +class GodotStringNamePrinter: + def __init__(self, value): + self.value = value + + def to_string(self): + return self.value["_data"]["name"]["_cowdata"]["_ptr"] + + # Hint that the object is string-like. + def display_hint(self): + return "string" + + +# Printer for Godot String variables. +class GodotStringPrinter: + def __init__(self, value): + self.value = value + + def to_string(self): + return self.value["_cowdata"]["_ptr"] + + # Hint that the object is string-like. + def display_hint(self): + return "string" + + +# Printer for Godot Vector variables. +class GodotVectorPrinter: + def __init__(self, value): + self.value = value + + # The COW (Copy On Write) object does a bunch of pointer arithmetic to access + # its members. + # The offsets are constants on the C++ side, optimized out, so not accessible to us. + # I'll just hard code the observed values and hope they are the same forever. + # See core/templates/cowdata.h + SIZE_OFFSET = 8 + DATA_OFFSET = 16 + + # Figures out the number of elements in the vector. + def get_size(self): + cowdata = self.value["_cowdata"] + if cowdata["_ptr"] == 0: + return 0 + else: + # The ptr member of cowdata does not point to the beginning of the + # cowdata. It points to the beginning of the data section of the cowdata. + # To get to the length section, we must back up to the beginning of the struct, + # then move back forward to the size. + # cf. CowData::_get_size + ptr = cowdata["_ptr"].cast(gdb.lookup_type("uint8_t").pointer()) + return int((ptr - self.DATA_OFFSET + self.SIZE_OFFSET).dereference()) + + # Lists children of the value, in this case the vector's items. + def children(self): + # Return nothing if ptr is null. + ptr = self.value["_cowdata"]["_ptr"] + if ptr == 0: + return + # Yield the items one by one. + for i in range(self.get_size()): + yield str(i), (ptr + i).dereference() + + def to_string(self): + return "%s [%d]" % (self.value.type.name, self.get_size()) + + # Hint that the object is array-like. + def display_hint(self): + return "array" + + +VECTOR_REGEX = re.compile("^Vector<.*$") + + +# Tries to find a pretty printer for a debugger value. +def lookup_pretty_printer(value): + if value.type.name == "StringName": + return GodotStringNamePrinter(value) + if value.type.name == "String": + return GodotStringPrinter(value) + if value.type.name and VECTOR_REGEX.match(value.type.name): + return GodotVectorPrinter(value) + return None + + +# Register our printer lookup function. +# The first parameter could be used to limit the scope of the printer +# to a specific object file, but that is unnecessary for us. +gdb.printing.register_pretty_printer(None, lookup_pretty_printer) |