Skip to content

Commit

Permalink
Merge pull request #1633 from heinezen/feature/gdb-pretty
Browse files Browse the repository at this point in the history
GDB pretty printers
  • Loading branch information
TheJJ authored Apr 18, 2024
2 parents 342ae05 + e79a403 commit efa919f
Show file tree
Hide file tree
Showing 6 changed files with 284 additions and 10 deletions.
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

0 comments on commit efa919f

Please sign in to comment.