diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c483f2247..8294ec495 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -3,6 +3,7 @@ # # Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, # 2020, 2021, 2022 Vladimír Vondruš +# Copyright © 2022 Guillaume Jacquemin # # Permission is hereby granted, free of charge, to any person obtaining a # copy of this software and associated documentation files (the "Software"), @@ -64,3 +65,5 @@ add_subdirectory(MagnumPlugins) # Magnum include dir for superprojects set(MAGNUM_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR} CACHE INTERNAL "" FORCE) + +add_subdirectory(debuggers) diff --git a/src/debuggers/CMakeLists.txt b/src/debuggers/CMakeLists.txt new file mode 100644 index 000000000..fc25ddcae --- /dev/null +++ b/src/debuggers/CMakeLists.txt @@ -0,0 +1,28 @@ +# +# This file is part of Magnum. +# +# Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, +# 2020, 2021, 2022 Vladimír Vondruš +# Copyright © 2022 Guillaume Jacquemin +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the "Software"), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +# DEALINGS IN THE SOFTWARE. +# + +install(DIRECTORY gdb + DESTINATION ${MAGNUM_DATA_INSTALL_DIR}/debuggers) diff --git a/src/debuggers/gdb/__init__.py b/src/debuggers/gdb/__init__.py new file mode 100644 index 000000000..d2092828a --- /dev/null +++ b/src/debuggers/gdb/__init__.py @@ -0,0 +1,36 @@ +# +# This file is part of Magnum. +# +# Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, +# 2020, 2021, 2022 Vladimír Vondruš +# Copyright © 2022 Guillaume Jacquemin +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the "Software"), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +# DEALINGS IN THE SOFTWARE. +# + +import gdb + +def register_magnum_printers(obj: gdb.Objfile | gdb.Progspace): + """Registers the pretty-printers for Magnum types""" + + if obj is None: + obj = gdb + + from .printers import register_magnum_printers + register_magnum_printers(obj) diff --git a/src/debuggers/gdb/printers.py b/src/debuggers/gdb/printers.py new file mode 100644 index 000000000..a6e768a5a --- /dev/null +++ b/src/debuggers/gdb/printers.py @@ -0,0 +1,400 @@ +# +# This file is part of Magnum. +# +# Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, +# 2020, 2021, 2022 Vladimír Vondruš +# Copyright © 2022 Guillaume Jacquemin +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the "Software"), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +# DEALINGS IN THE SOFTWARE. +# + +import sys +import gdb, gdb.printing +import struct + +class MagnumTypePrinter: + """Base class for all pretty-printers""" + + def __init__(self, val: gdb.Value): + self.val = val + self.enabled: bool = True + + def to_string(self): + return f"{self.val.type}" + +class MagnumImage(MagnumTypePrinter): + """Prints a Magnum::(Compressed)Image(View)""" + + def to_string(self): + return f"{self.val.type} of size {self.val['_size']} and format {self.val['_format']}" + +class MagnumResource(MagnumTypePrinter): + """Prints a Resource""" + + def to_string(self): + return f"Resource key {self.val['_key']} (state: {self.val['_state']})" + +class MagnumResourceKey(MagnumTypePrinter): + """Prints a ResourceKey""" + + def to_string(self): + key = str() + for i in range(8): + key += "{:02x}".format(int(self.val['_digest'][i]) & 0xff) + return "".join(key) + +class MagnumTimeline(MagnumTypePrinter): + """Prints a Timeline""" + + def to_string(self): + if not bool(self.val['running']): + return f"{self.val.type} (not running)" + return f"{self.val.type} (running; previous frame time: {self.val['_previousFrameDuration']}s)" + +class MagnumTrack(MagnumTypePrinter): + """Prints an Animation::Track""" + + class Iterator: + def __init__(self, val: gdb.Value, fields: list[gdb.Field]): + self.val = val + self.fields = fields + self.index = 0 + + def __iter__(self): + return self + + def __next__(self): + index = self.index + self.index = self.index + 1 + if index == len(self.fields): + raise StopIteration + return (f"[{self.fields[index].name.split('_')[1]}]", self.val[self.fields[index]]) + + def children(self): + return self.Iterator(self.val, self.val.type.fields()) + + def to_string(self): + return f"{self.val.type} with {self.val['_data']['_size']} keyframes" + +class MagnumAngle(MagnumTypePrinter): + """Prints a Math::Deg or Rad""" + + def to_string(self): + return f"{self.val['_value']}" + +class MagnumBezier(MagnumTypePrinter): + """Prints a Math::Bezier""" + + class Iterator: + def __init__(self, data: gdb.Value, order: int): + self.data = data + self.order = order + self.count: int = 0 + + def __iter__(self): + return self + + def __next__(self): + count = self.count + self.count = self.count + 1 + if count == self.order + 1: + raise StopIteration + return (f"[{count}]", self.data[count]) + + def __init__(self, val: gdb.Value): + template_arguments = val.type.strip_typedefs().name.split('<')[1].split('>')[0].split(',') + self.order = int(template_arguments[0].strip()) + self.dimensions = int(template_arguments[1].strip()) + super(MagnumBezier, self).__init__(val) + + def children(self): + return self.Iterator(self.val['_data'], self.order) + + def to_string(self): + return f"{self.order}-order, {self.dimensions}-dimensional Bézier curve" + +class MagnumBitVector(MagnumTypePrinter): + """Prints a Math::BitVector""" + + class Iterator: + def __init__(self, data: gdb.Value, size: int): + self.data = data + self.size = size + self.count = 0 + + def __iter__(self): + return self + + def __next__(self): + count = self.count + self.count = self.count + 1 + if count == self.size: + raise StopIteration + return (f"[{count}]", bool((self.data[int(count/8)] >> count%8) & 0x01)) + + def __init__(self, val: gdb.Value): + self.size = int(val.type.strip_typedefs().name.split('<')[1].split('>')[0]) + super(MagnumBitVector, self).__init__(val) + + def children(self): + return self.Iterator(self.val['_data'], self.size) + + def to_string(self): + return f"BitVector of size {self.size}" + +class MagnumComplex(MagnumTypePrinter): + """Prints a Math::Complex""" + + def to_string(self): + return f"{self.val['_real']} + i{self.val['_imaginary']}" + +class MagnumCubicHermite(MagnumTypePrinter): + """Prints a Math::CubicHermite""" + + def to_string(self): + return f"Cubic Hermite curve with spline point {self.val['_point']}, in-tangent {self.val['_inTangent']}," \ + f" and out-tangent {self.val['_outTangent']}" + +class MagnumDual(MagnumTypePrinter): + """Prints a Math::Dual{Complex,Quaternion}""" + + def to_string(self): + return f"({self.val['_real']}) + \u03F5({self.val['_dual']})" + +class MagnumFrustum(MagnumTypePrinter): + """Prints a Math::Frustum""" + + class Iterator: + LABELS = ["left", "right", "bottom", "top", "near", "far"] + SIZE = len(LABELS) + + def __init__(self, data: gdb.Value): + self.data = data + self.count = 0 + + def __iter__(self): + return self + + def __next__(self): + count = self.count + self.count = self.count + 1 + if count == self.SIZE: + raise StopIteration + return (f"[{self.LABELS[count]}]", self.data[count]) + + def children(self): + return self.Iterator(self.val['_data']) + +class MagnumHalf(MagnumTypePrinter): + """Prints a Math::Half""" + + def to_string(self): + data = int(self.val['_data']).to_bytes(2, byteorder=sys.byteorder) + return f"{struct.unpack('e', data)[0]}" + +class MagnumMatrix(MagnumTypePrinter): + """Prints a Math::(Rectangular)Matrix""" + + class Iterator: + def __init__(self, data: gdb.Value, rows: int): + self.data = data + self.rows = rows + self.row = 0 + + def __iter__(self): + return self + + def __next__(self): + row = self.row + self.row = self.row + 1 + if row == self.rows: + raise StopIteration + return (f"[{row}]", self.data[row]) + + def __init__(self, val: gdb.Value): + try: + # (Rectangular)Matrix + self.rows = int(val.type.strip_typedefs().name.split('<')[1].split(',')[0].strip()) + except ValueError: + # Matrix{3,4} + self.rows = int(val.type.strip_typedefs().name.split('<')[0][-1]) + + if "RectangularMatrix" in val.type.strip_typedefs().name: + self.columns = int(val.type.strip_typedefs().name.split('<')[1].split(',')[1].strip()) + else: + self.columns = self.rows + + super(MagnumMatrix, self).__init__(val) + + def children(self): + return self.Iterator(self.val['_data'], self.rows) + + def to_string(self): + return f"Matrix with {self.rows} rows and {self.columns} columns" + +class MagnumQuaternion(MagnumTypePrinter): + """Prints a Math::Quaternion""" + + def to_string(self): + return f"[{self.val['_vector']}, {self.val['_scalar']}]" + +class MagnumRange(MagnumTypePrinter): + """Prints a Math::Range({2,3}D)""" + + def to_string(self): + return f"Range (min: {self.val['_min']}, max: {self.val['_max']})" + +class MagnumVectorColor(MagnumTypePrinter): + """ + Prints a Math::Vector({2,3,4}). + + Also prints Math::Color{3,4}, because they inherit from the vectors. + """ + + def __init__(self, val: gdb.Value): + try: + # Vector2/3/4 and Color3/4 + self.dimensions = int(val.type.strip_typedefs().name.split('<')[0][-1]) + except ValueError: + # Vector, used in matrices + self.dimensions = int(val.type.strip_typedefs().name.split('<')[1].split(',')[0].strip()) + super(MagnumVectorColor, self).__init__(val) + + def to_string(self): + components = [] + for i in range(self.dimensions): + components.append(str(self.val['_data'][i])) + return f"({', '.join(components)})" + +magnum_printers: gdb.printing.RegexpCollectionPrettyPrinter | None = None + +def build_magnum_printer(): + global magnum_printers + + if magnum_printers is not None: + return + + magnum_printers = gdb.printing.RegexpCollectionPrettyPrinter("Magnum") + magnum_printers.add_printer("CompressedImage", + "^Magnum::CompressedImage<\d+, .*>$", + MagnumImage) + magnum_printers.add_printer("CompressedImageView", + "^Magnum::CompressedImageView<\d+, .*>$", + MagnumImage) + magnum_printers.add_printer("Image", + "^Magnum::Image<\d+, .*>$", + MagnumImage) + magnum_printers.add_printer("ImageView", + "^Magnum::ImageView<\d+, .*>$", + MagnumImage) + magnum_printers.add_printer("Resource", + "^Magnum::Resource<.*, .*>$", + MagnumResource) + magnum_printers.add_printer("ResourceKey", + "^Magnum::ResourceKey$", + MagnumResourceKey) + magnum_printers.add_printer("Timeline", + "^Magnum::Timeline$", + MagnumTimeline) + magnum_printers.add_printer("Animation::Track", + "^Magnum::Animation::Track<.*>$", + MagnumTrack) + magnum_printers.add_printer("Math::Bezier", + "^Magnum::Math::Bezier<\d+, \d+, .*>$", + MagnumBezier) + magnum_printers.add_printer("Math::BitVector", + "^Magnum::Math::BitVector<\d+>$", + MagnumBitVector) + magnum_printers.add_printer("Math::Color3", + "^Magnum::Math::Color3<.*>$", + MagnumVectorColor) + magnum_printers.add_printer("Math::Color4", + "^Magnum::Math::Color4<.*>$", + MagnumVectorColor) + magnum_printers.add_printer("Math::Complex", + "^Magnum::Math::Complex<.*>$", + MagnumComplex) + magnum_printers.add_printer("Math::CubicHermite", + "^Magnum::Math::CubicHermite<.*>$", + MagnumCubicHermite) + magnum_printers.add_printer("Math::Deg", + "^Magnum::Math::Deg<.*>$", + MagnumAngle) + magnum_printers.add_printer("Math::DualComplex", + "^Magnum::Math::DualComplex<.*>$", + MagnumDual) + magnum_printers.add_printer("Math::DualQuaternion", + "^Magnum::Math::DualQuaternion<.*>$", + MagnumDual) + magnum_printers.add_printer("Math::Frustum", + "^Magnum::Math::Frustum<.*>$", + MagnumFrustum) + magnum_printers.add_printer("Math::Half", + "^Magnum::Math::Half$", + MagnumHalf) + magnum_printers.add_printer("Math::Matrix", + "^Magnum::Math::Matrix<\d+, .*>$", + MagnumMatrix) + magnum_printers.add_printer("Math::Matrix3", + "^Magnum::Math::Matrix3<.*>$", + MagnumMatrix) + magnum_printers.add_printer("Math::Matrix4", + "^Magnum::Math::Matrix4<.*>$", + MagnumMatrix) + magnum_printers.add_printer("Math::Quaternion", + "^Magnum::Math::Quaternion<.*>$", + MagnumQuaternion) + magnum_printers.add_printer("Math::Rad", + "^Magnum::Math::Rad<.*>$", + MagnumAngle) + magnum_printers.add_printer("Math::Range", + "^Magnum::Math::Range<\d+, .*>$", + MagnumRange) + magnum_printers.add_printer("Math::Range2D", + "^Magnum::Math::Range2D<.*>$", + MagnumRange) + magnum_printers.add_printer("Math::Range3D", + "^Magnum::Math::Range3D<.*>$", + MagnumRange) + magnum_printers.add_printer("Math::RectangularMatrix", + "^Magnum::Math::RectangularMatrix<\d+, \d+, .*>$", + MagnumMatrix) + magnum_printers.add_printer("Math::Vector", + "^Magnum::Math::Vector<\d+,.*>$", + MagnumVectorColor) + magnum_printers.add_printer("Math::Vector2", + "^Magnum::Math::Vector2<.*>$", + MagnumVectorColor) + magnum_printers.add_printer("Math::Vector3", + "^Magnum::Math::Vector3<.*>$", + MagnumVectorColor) + magnum_printers.add_printer("Math::Vector4", + "^Magnum::Math::Vector4<.*>$", + MagnumVectorColor) + +def register_magnum_printers(obj: gdb.Objfile | gdb.Progspace): + if obj is None: + obj = gdb + + global magnum_printers + + if magnum_printers is None: + build_magnum_printer() + + gdb.printing.register_pretty_printer(obj, magnum_printers)