Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GDB pretty printers #1633

Merged
merged 12 commits into from
Apr 18, 2024
12 changes: 6 additions & 6 deletions buildsystem/codecompliance/__main__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright 2014-2023 the openage authors. See copying.md for legal info.
# Copyright 2014-2024 the openage authors. See copying.md for legal info.

"""
Entry point for the code compliance checker.
Expand Down Expand Up @@ -231,7 +231,7 @@ def find_all_issues(args, check_files=None):

if args.pystyle:
from .pystyle import find_issues
yield from find_issues(check_files, ('openage', 'buildsystem'))
yield from find_issues(check_files, ('openage', 'buildsystem', 'etc/gdb_pretty'))

if args.cython:
from buildsystem.codecompliance.cython import find_issues
Expand All @@ -243,12 +243,12 @@ def find_all_issues(args, check_files=None):

if args.pylint:
from .pylint import find_issues
yield from find_issues(check_files, ('openage', 'buildsystem'))
yield from find_issues(check_files, ('openage', 'buildsystem', 'etc/gdb_pretty'))

if args.textfiles:
from .textfiles import find_issues
yield from find_issues(
('openage', 'libopenage', 'buildsystem', 'doc', 'legal'),
('openage', 'libopenage', 'buildsystem', 'doc', 'legal', 'etc/gdb_pretty'),
('.pxd', '.pyx', '.pxi', '.py',
'.h', '.cpp', '.template',
'', '.txt', '.md', '.conf',
Expand All @@ -257,13 +257,13 @@ def find_all_issues(args, check_files=None):
if args.legal:
from .legal import find_issues
yield from find_issues(check_files,
('openage', 'buildsystem', 'libopenage'),
('openage', 'buildsystem', 'libopenage', 'etc/gdb_pretty'),
args.test_git_change_years)

if args.filemodes:
from .modes import find_issues
yield from find_issues(check_files, ('openage', 'buildsystem',
'libopenage'))
'libopenage', 'etc/gdb_pretty'))


if __name__ == '__main__':
Expand Down
22 changes: 20 additions & 2 deletions doc/debug.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,28 @@ gdb -ex 'set breakpoint pending on' -ex 'b openage::run_game' -ex run --args run
```
The game will be paused at the start of the function run_game() located in `libopenage/main.cpp`

#### Note:
The `run` executable is a compiled version of `run.py` that also embeds the interpreter.
**Note:** The `run` executable is a compiled version of `run.py` that also embeds the interpreter.
The game is intended to be run by `run.py` but it is much easier to debug the `./run` file

### Pretty Printers

Enabling pretty printing will make GDB's output much more readable, so we always recommend
to configure it in your setup. Your [favourite IDE](/doc/ide/) probably an option to enable pretty printers
for the standard library types. If not, you can get them from the [gcc repository](https://github.com/gcc-mirror/gcc/tree/master/libstdc%2B%2B-v3/python/libstdcxx) and register them in your local `.gdbinit` file.

Additionally, we have created several custom GDB pretty printers for types used in `libopenage`,
the C++ library that contains the openage engine core. To enable them, you have to load the project's
own init file [openage.gdbinit](/etc/openage.gdbinit) when running GDB:

```gdb
(gdb) source <path-of-openage-dir>/etc/openage.gdbinit
```

Your IDE may be able to do this automatically for a debug run. Alternatively, you can configure
an [auto-loader](https://sourceware.org/gdb/current/onlinedocs/gdb.html/Python-Auto_002dloading.html#Python-Auto_002dloading)
that loads the scripts for you.


### GDBGUI

[gdbgui](https://github.com/cs01/gdbgui) is a browser-based frontend for GDB.
Expand Down
5 changes: 5 additions & 0 deletions etc/gdb_pretty/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Copyright 2024-2024 the openage authors. See copying.md for legal info.

"""
GDB pretty printers for openage.
"""
241 changes: 241 additions & 0 deletions etc/gdb_pretty/printers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
# Copyright 2024-2024 the openage authors. See copying.md for legal info.

"""
Pretty printers for GDB.
"""

import re
import gdb # type: ignore


class PrinterControl(gdb.printing.PrettyPrinter):
"""
Exposes a pretty printer for a specific type.

Printer are searched in the following order:
1. Exact type name _with_ typedefs
2. Regex of type name _without_ typedefs
"""

def __init__(self, name: str):
super().__init__(name)

self.name_printers = {}
self.regex_printers = {}

def add_printer(self, type_name: str, printer):
"""
Adds a printer for a specific type name.
"""
self.name_printers[type_name] = printer

def add_printer_regex(self, regex: str, printer):
"""
Adds a printer for a specific type name.

:param regex: The regex to match the type name.
:type regex: str
"""
self.regex_printers[re.compile(regex)] = printer

def __call__(self, val: gdb.Value):
# Check the exact type name with typedefa
type_name = val.type.name
if type_name in self.name_printers:
return self.name_printers[val.type.name](val)

# Check the type name without typedefs and regex
type_name = val.type.unqualified().strip_typedefs().tag
if type_name is None:
return None

for regex, printer in self.regex_printers.items():
if regex.match(type_name):
return printer(val)

return None


pp = PrinterControl('openage')
gdb.printing.register_pretty_printer(None, pp)


def printer_typedef(type_name: str):
"""
Decorator for pretty printers.

:param type_name: The name of the type to register the printer for.
:type type_name: str
"""
def _register_printer(printer):
"""
Registers the printer with GDB.
"""
pp.add_printer(type_name, printer)

return _register_printer


def printer_regex(regex: str):
"""
Decorator for pretty printers.

:param regex: The regex to match the type name.
:type regex: str
"""
def _register_printer(printer):
"""
Registers the printer with GDB.
"""
pp.add_printer_regex(regex, printer)

return _register_printer


@printer_typedef('openage::time::time_t')
class TimePrinter:
"""
Pretty printer for openage::time::time_t.

TODO: Inherit from gdb.ValuePrinter when gdb 14.1 is available in all distros.
"""

def __init__(self, val: gdb.Value):
self.__val = val

def to_string(self):
"""
Get the time as a string.

Format: SS.sss (e.g. 12.345s)
"""
fractional_bits = int(self.__val.type.template_argument(1))

# convert the fixed point value to double
to_double_factor = 1 / pow(2, fractional_bits)
seconds = float(self.__val['raw_value']) * to_double_factor
# show as seconds with millisecond precision
return f'{seconds:.3f}s'

def children(self):
"""
Get the displayed children of the time value.
"""
yield ('raw_value', self.__val['raw_value'])


@printer_regex('^openage::util::FixedPoint<.*>')
class FixedPointPrinter:
"""
Pretty printer for openage::util::FixedPoint.

TODO: Inherit from gdb.ValuePrinter when gdb 14.1 is available in all distros.
"""

def __init__(self, val: gdb.Value):
self.__val = val

def to_string(self):
"""
Get the fixed point value as a string.

Format: 0.12345
"""
fractional_bits = int(self.__val.type.template_argument(1))

# convert the fixed point value to double
to_double_factor = 1 / pow(2, fractional_bits)
num = float(self.__val['raw_value']) * to_double_factor
return f'{num:.5f}'

def children(self):
"""
Get the displayed children of the fixed point value.
"""
yield ('raw_value', self.__val['raw_value'])

# calculate the precision of the fixed point value
# 16 * log10(2) = 16 * 0.30103 = 4.81648
# do this manualy because it's usually optimized out by the compiler
fractional_bits = int(self.__val.type.template_argument(1))

precision = int(fractional_bits * 0.30103 + 1)
yield ('approx_precision', precision)


@printer_regex('^openage::util::Vector<.*>')
class VectorPrinter:
"""
Pretty printer for openage::util::Vector.

TODO: Inherit from gdb.ValuePrinter when gdb 14.1 is available in all distros.
"""

def __init__(self, val: gdb.Value):
self.__val = val

def to_string(self):
"""
Get the vector as a string.
"""
size = self.__val.type.template_argument(0)
int_type = self.__val.type.template_argument(1)
return f'openage::util::Vector<{size}, {int_type}>'

def children(self):
"""
Get the displayed children of the vector.
"""
size = self.__val.type.template_argument(0)
for i in range(size):
yield (str(i), self.__val['_M_elems'][i])

def child(self, index):
"""
Get the child at the given index.
"""
return self.__val['_M_elems'][index]

def num_children(self):
"""
Get the number of children of the vector.
"""
return self.__val.type.template_argument(0)

@staticmethod
def display_hint():
"""
Get the display hint for the vector.
"""
return 'array'


@printer_regex('^openage::curve::Keyframe<.*>')
class KeyframePrinter:
"""
Pretty printer for openage::curve::Keyframe.

TODO: Inherit from gdb.ValuePrinter when gdb 14.1 is available in all distros.
"""

def __init__(self, val: gdb.Value):
self.__val = val

def to_string(self):
"""
Get the keyframe as a string.
"""
return f'openage::curve::Keyframe<{self.__val.type.template_argument(0)}>'

def children(self):
"""
Get the displayed children of the keyframe.
"""
yield ('time', self.__val['time'])
yield ('value', self.__val['value'])

# TODO: curve types
# TODO: coord types
# TODO: pathfinding types
# TODO: input event codes
# TODO: eigen types https://github.com/dmillard/eigengdb
10 changes: 10 additions & 0 deletions etc/openage.gdbinit
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
python
import sys, os

print("Loading openage.gdbinit")
print(f"Adding custom pretty-printers directory to the GDB path: {os.getcwd() + '../../etc'}")

sys.path.insert(0, "../../etc")

import gdb_pretty.printers
end
4 changes: 2 additions & 2 deletions libopenage/gamestate/simulation.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2013-2023 the openage authors. See copying.md for legal info.
// Copyright 2013-2024 the openage authors. See copying.md for legal info.

#include "simulation.h"

Expand Down Expand Up @@ -45,7 +45,7 @@ GameSimulation::GameSimulation(const util::Path &root_dir,
void GameSimulation::run() {
this->start();
while (this->running) {
auto current_time = this->time_loop->get_clock()->get_time();
time::time_t current_time = this->time_loop->get_clock()->get_time();
this->event_loop->reach_time(current_time, this->game->get_state());
}
log::log(MSG(info) << "Game simulation loop exited");
Expand Down
Loading