Merge pull request 'Add GDB pretty-printers for basic types (PIString, PIVector, PIMap, PIByteArray, etc.)' (#213) from gdb_pretty_printers into master

Reviewed-on: #213
This commit was merged in pull request #213.
This commit is contained in:
2026-08-27 20:11:00 +03:00
9 changed files with 1082 additions and 3 deletions
+1
View File
@@ -8,3 +8,4 @@ CMakeLists.txt.user*
/build* /build*
/AGENTS.md /AGENTS.md
/plans /plans
__pycache__
+27 -1
View File
@@ -699,7 +699,13 @@ endif()
# Install # Install
# Check if system or local install will be used (to system install use "-DLIB=" argument of cmake)
# GDB pretty-printers (see tools/gdb/): module + auto-load stub named after the library
set(PIP_GDB_PY "${CMAKE_CURRENT_SOURCE_DIR}/tools/gdb/pip_pp.py")
set(PIP_GDB_STUB "${CMAKE_CURRENT_BINARY_DIR}/gdb/libpip.so-gdb.py")
set(PIP_GDB_PY_RELPATH "../share/pip/gdb")
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/tools/gdb/gdb_autoload_stub.py.in" "${PIP_GDB_STUB}" @ONLY)
if(NOT LOCAL) if(NOT LOCAL)
if(WIN32) if(WIN32)
if(MINGW) if(MINGW)
@@ -738,6 +744,17 @@ if(NOT LOCAL)
endif() endif()
install(TARGETS ${PIP_MODULES} DESTINATION ${CMAKE_INSTALL_PREFIX}/lib) install(TARGETS ${PIP_MODULES} DESTINATION ${CMAKE_INSTALL_PREFIX}/lib)
endif() endif()
# GDB pretty-printers
if (NOT CROSSTOOLS)
install(FILES "${PIP_GDB_PY}" DESTINATION ${CMAKE_INSTALL_PREFIX}/share/pip/gdb)
if (PIP_LIB_TYPE STREQUAL "SHARED")
if(WIN32)
install(FILES "${PIP_GDB_STUB}" DESTINATION ${CMAKE_INSTALL_PREFIX}/bin RENAME "$<TARGET_FILE_NAME:pip>-gdb.py")
else()
install(FILES "${PIP_GDB_STUB}" DESTINATION ${CMAKE_INSTALL_PREFIX}/lib RENAME "$<TARGET_FILE_NAME:pip>-gdb.py")
endif()
endif()
endif()
else() else()
if(NOT PIP_FREERTOS) if(NOT PIP_FREERTOS)
if(WIN32) if(WIN32)
@@ -754,6 +771,15 @@ else()
install(DIRECTORY ${HDR_DIRS} DESTINATION include/pip) install(DIRECTORY ${HDR_DIRS} DESTINATION include/pip)
endif() endif()
endif() endif()
# GDB pretty-printers
install(FILES "${PIP_GDB_PY}" DESTINATION share/pip/gdb)
if (PIP_LIB_TYPE STREQUAL "SHARED")
if(WIN32)
install(FILES "${PIP_GDB_STUB}" DESTINATION bin RENAME "$<TARGET_FILE_NAME:pip>-gdb.py")
else()
install(FILES "${PIP_GDB_STUB}" DESTINATION lib RENAME "$<TARGET_FILE_NAME:pip>-gdb.py")
endif()
endif()
endif() endif()
file(GLOB CMAKES "cmake/*.cmake" "cmake/*.in") file(GLOB CMAKES "cmake/*.cmake" "cmake/*.in")
install(FILES ${CMAKES} DESTINATION ${CMAKE_ROOT}/Modules) install(FILES ${CMAKES} DESTINATION ${CMAKE_ROOT}/Modules)
+187
View File
@@ -0,0 +1,187 @@
\~english \page debugging Debugging with GDB
\~russian \page debugging Отладка в GDB
\~english
PIP ships a GDB pretty-printer script, `tools/gdb/pip_pp.py`, that makes the basic types print their content instead of their raw internals:
| Type | Example output |
|------|----------------|
| \a PIString | `"hello"` |
| \a PIByteArray | `{0x48, 0x65, 0x6c, 0x6c, 0x6f}` |
| \a PIChar | `'a'` or `U+043c` |
| \a PIVector<T> | `{1, 2, 3}` |
| \a PIDeque<T> | `{1, 2, 3}` |
| \a PIStringList | `{"one", "two"}` |
| \a PIMap<K,V> | `{"x": 1, "y": 2}` |
| \a PISet<T> | `{3, 5}` |
| \a PIPair<K,V> | `{"k", 7}` |
| \a PIVariant | `PIVariant(pivInt, 4 bytes)` |
| \a PINetworkAddress | `192.168.1.10:8080` |
Nested containers are printed recursively, e.g. `PIVector<PIString>` prints as `{"a", "bb"}`.
Pointers to the covered types show the address and the content:
`(PIString *) 0x7fffffffdac0 "hello"`.
Printers are read-only and defensive: on any error GDB falls back to the
default (raw) printing. Long containers are truncated, only the first
32 elements are printed by default (the tail is shown as
`..., ... (N total)`); the limit is the `_MAX_ELEMENTS` constant at the
top of `pip_pp.py`.
Requirements: the executable and the PIP library must be built with debug
information (`-g`).
\section loading Loading the pretty-printers
Manual (works with any build tree):
\code{.gdb}
(gdb) source /path/to/pip/tools/gdb/pip_pp.py
\endcode
Run it after the executable is loaded (in a session started as
`gdb ./program`, or after the `file` command).
Automatic: CMake install places the script at
`<prefix>/share/pip/gdb/pip_pp.py` and an auto-load stub
`<prefix>/lib/libpip.so-gdb.py` next to the library. GDB evaluates the stub
automatically when libpip.so is loaded (see GDB manual, "Auto-loading
extensions"). If the library is installed outside of GDB's default auto-load
safe path, add the directory once:
\code{.gdb}
(gdb) add-auto-load-safe-path /your/prefix/lib
\endcode
Or create <homedir>/.gdbinit file and write to it next line
\code
add-auto-load-safe-path <root_dir>
\endcode
, where root_dir is top-level directory contains library and pip_pp.py script.
The stub is installed only for shared builds; for static builds
use the manual `source` command.
\section qtcreator Qt Creator
Qt Creator's Variables/Expressions views do not use GDB
pretty-printers; they run their own Python dumper. PIP provides the
dumper module `tools/qtcreator/pip_types.py`, which covers the same
types as `pip_pp.py`. To enable it:
1. Open the debugger settings (Qt Creator -> Settings -> Debugger).
2. Switch to the "Variables and expressions" tab.
3. In the "Extra dumper file" field (tooltip: "Path to a Python file
containing additional data dumpers"), enter the path to the file:
\code
/path/to/pip/tools/qtcreator/pip_types.py
\endcode
After that PIString, PIVector<T>, PIMap<K,V> and the other types from
the table above show their content in the Variables/Expressions views.
No changes to the GDB setup (`.gdbinit`, auto-load stub) are needed for
Qt Creator; those keep working for the command-line GDB.
\section verifying Verifying the installation
A check script builds a demo with all covered types and asserts the expected
GDB output:
\code{.bash}
tools/gdb/test/test_pp.sh build_linux
\endcode
\~russian
Вместе с PIP поставляется скрипт pretty-printer'ов для GDB, `tools/gdb/pip_pp.py`,
который выводит содержимое базовых типов вместо их внутренних полей:
| Тип | Пример вывода |
|-----|---------------|
| \a PIString | `"hello"` |
| \a PIByteArray | `{0x48, 0x65, 0x6c, 0x6c, 0x6f}` |
| \a PIChar | `'a'` или `U+043c` |
| \a PIVector<T> | `{1, 2, 3}` |
| \a PIDeque<T> | `{1, 2, 3}` |
| \a PIStringList | `{"one", "two"}` |
| \a PIMap<K,V> | `{"x": 1, "y": 2}` |
| \a PISet<T> | `{3, 5}` |
| \a PIPair<K,V> | `{"k", 7}` |
| \a PIVariant | `PIVariant(pivInt, 4 bytes)` |
| \a PINetworkAddress | `192.168.1.10:8080` |
Вложенные контейнеры выводятся рекурсивно, например `PIVector<PIString>`
выводится как `{"a", "bb"}`. Указатели на поддерживаемые типы показывают
адрес и содержимое: `(PIString *) 0x7fffffffdac0 "hello"`.
Принтеры только читают память и защищены от ошибок: при любом сбое GDB
возвращается к стандартному (сырому) выводу. Длинные контейнеры обрезаются,
по умолчанию выводится первые 32 элемента (хвост показывается как
`..., ... (N total)`); ограничение задаётся константой `_MAX_ELEMENTS`
в начале файла `pip_pp.py`.
Требование: исполняемый файл и библиотека PIP должны быть собраны с
отладочной информацией (`-g`).
\section loading Загрузка принтеров
Вручную (работает с любым деревом сборки):
\code{.gdb}
(gdb) source /path/to/pip/tools/gdb/pip_pp.py
\endcode
Выполнять после загрузки исполняемого файла (в сессии, запущенной как
`gdb ./program`, или после команды `file`).
Автоматически: CMake install ставит скрипт в
`<prefix>/share/pip/gdb/pip_pp.py` и auto-load заглушку
`<prefix>/lib/libpip.so-gdb.py` рядом с библиотекой. GDB выполняет заглушку
автоматически при загрузке libpip.so (см. руководство GDB, "Auto-loading
extensions"). Если библиотека установлена вне стандартного auto-load safe path
GDB, добавьте каталог один раз:
\code{.gdb}
(gdb) add-auto-load-safe-path /your/prefix/lib
\endcode
Или создайте <homedir>/.gdbinit файл и запишите туда строку
\code
add-auto-load-safe-path <root_dir>
\endcode
, где root_dir - это корневая директория, в рамках которой находятся библиотека и скрипт pip_pp.py.
Заглушка устанавливается только для shared-сборок; для статических сборок
используйте ручной `source`.
\section qtcreator Qt Creator
Окна Variables/Expressions в Qt Creator не используют pretty-printer'ы
GDB — у них собственный Python-дампер. PIP предоставляет модуль дампера
`tools/qtcreator/pip_types.py`, который покрывает те же типы, что и
`pip_pp.py`. Для включения:
1. Откройте настройки отладчика (Qt Creator -> Настройки -> Отладчик).
2. Переключитесь на вкладку «Переменные и выражения».
3. В поле "Extra Debugging Helper" укажите путь к файлу:
\code
/path/to/pip/tools/qtcreator/pip_types.py
\endcode
После этого PIString, PIVector<T>, PIMap<K,V> и другие типы из
таблицы выше будут показывать своё содержимое в окнах
«Переменные» и «Выражения». Изменения в настройках GDB (`.gdbinit`,
auto-load заглушка) для Qt Creator не нужны; они продолжают работать
для GDB из командной строки.
\section verifying Проверка установки
Скрипт проверки собирает демо со всеми поддерживаемыми типами и сверяет
ожидаемый вывод GDB:
\code{.bash}
tools/gdb/test/test_pp.sh build_linux
\endcode
+2 -2
View File
@@ -20,7 +20,7 @@ serialize custom types with version back-compatibility.
Summary one can find at \ref summary page. Summary one can find at \ref summary page.
Basic using — \ref using_basic. Further topics — \ref using_advanced. Configuration — \ref config. Code generation — \ref code_model. Streams: \ref iostream, \ref chunk_stream. State machine — \ref state_machine. Complex I/O — \ref connection. TCP client-server — \ref client_server. Tiling console — \ref console. Application tools — \ref application. Threading — \ref threading. Examples — \ref examples. Basic using — \ref using_basic. Further topics — \ref using_advanced. Configuration — \ref config. Code generation — \ref code_model. Streams: \ref iostream, \ref chunk_stream. State machine — \ref state_machine. Complex I/O — \ref connection. TCP client-server — \ref client_server. Tiling console — \ref console. Application tools — \ref application. Threading — \ref threading. Debugging — \ref debugging. Examples — \ref examples.
\~russian \~russian
@@ -41,4 +41,4 @@ PIP также тесно интегрируется с системой сбо
Сводку можно найти на странице \ref summary. Сводку можно найти на странице \ref summary.
Базовое использование — \ref using_basic. Дополнительные темы — \ref using_advanced. Конфигурация — \ref config. Кодогенерация — \ref code_model. Потоки: \ref iostream, \ref chunk_stream. Машина состояний — \ref state_machine. Сложный ввод-вывод — \ref connection. TCP клиент-сервер — \ref client_server. Тайлинговая консоль — \ref console. Инструменты приложения — \ref application. Многопоточность — \ref threading. Примеры — \ref examples. Базовое использование — \ref using_basic. Дополнительные темы — \ref using_advanced. Конфигурация — \ref config. Кодогенерация — \ref code_model. Потоки: \ref iostream, \ref chunk_stream. Машина состояний — \ref state_machine. Сложный ввод-вывод — \ref connection. TCP клиент-сервер — \ref client_server. Тайлинговая консоль — \ref console. Инструменты приложения — \ref application. Многопоточность — \ref threading. Отладка — \ref debugging. Примеры — \ref examples.
+14
View File
@@ -0,0 +1,14 @@
# GDB auto-load stub for the PIP library pretty-printers.
#
# GDB evaluates this file automatically when the shared library it is named
# after (libpip.so) is loaded, see GDB manual "Auto-loading extensions".
#
# Generated by CMake from tools/gdb/gdb_autoload_stub.py.in. Do not edit.
import os
import sys
_here = os.path.dirname(os.path.realpath(__file__))
sys.path.insert(0, os.path.join(_here, "@PIP_GDB_PY_RELPATH@"))
import pip_pp
pip_pp.register_printers(gdb.current_objfile())
+375
View File
@@ -0,0 +1,375 @@
"""
GDB pretty-printers for the PIP (Platform-Independent Primitives) library.
Covered types:
PIString, PIByteArray, PIChar, PIVector<T>, PIDeque<T>,
PIStringList, PIMap<K,V>, PISet<T>, PIPair<K,V>, PIVariant,
PINetworkAddress
Usage
-----
1. Manual (works for any build, requires debug info -g):
(gdb) source /path/to/pip_pp.py
Load the file after the executable (or library) is loaded, e.g. in a
session started as ``gdb ./program`` or after the ``file`` command.
2. Automatic: CMake install places this file at
``<prefix>/share/pip/gdb/pip_pp.py`` and an auto-load stub
``<libdir>/libpip.so-gdb.py`` next to the library. GDB evaluates the
stub automatically when libpip.so is loaded (see GDB manual,
"Auto-loading extensions"). If the library is installed outside of the
default auto-load safe path, run once in gdb:
(gdb) add-auto-load-safe-path /your/lib/dir
All printers are read-only and defensive: on any error they return None
and GDB falls back to its default (raw) printing.
Long containers are truncated: only the first _MAX_ELEMENTS elements are
printed (default 32; change the constant at the top of this file), the
tail is shown as `..., ... (N total)`.
Output examples:
(gdb) p s
$1 = "hello"
(gdb) p v
$2 = {1, 2, 3}
(gdb) p m
$3 = {"a": 1, "b": 2}
(gdb) p &s
$4 = (PIString *) 0x7fffffffdac0 "hello"
"""
import re
import gdb
# Absolute safety cap: never try to read more elements than this
# (protects against corrupted size fields).
_MAX_READ = 10000000
# Maximum number of elements printed per container. Change to taste.
_MAX_ELEMENTS = 32
def _max_elements():
return max(1, _MAX_ELEMENTS)
# ---------------------------------------------------------------------------
# helpers
# ---------------------------------------------------------------------------
def _c_type_name(t):
"""Build a C-style name for a type (handles pointers recursively)."""
t = t.unqualified()
if t.code == gdb.TYPE_CODE_PTR:
return _c_type_name(t.target()) + " *"
return t.tag if t.tag is not None else t.name
def _escape_char(c):
"""Escape a single character for a GDB C-style string literal."""
o = ord(c)
if c == '"':
return '\\"'
if c == "\\":
return "\\\\"
if c == "\n":
return "\\n"
if c == "\t":
return "\\t"
if c == "\r":
return "\\r"
if o < 0x20 or o == 0x7F:
return "\\x%02x" % o
return c
def _utf16_to_string(units):
"""Build a GDB string literal from a list of UTF-16 code units."""
out = []
i = 0
n = len(units)
while i < n:
u = units[i]
# combine surrogate pairs
if 0xD800 <= u <= 0xDBFF and i + 1 < n and 0xDC00 <= units[i + 1] <= 0xDFFF:
u = 0x10000 + ((u - 0xD800) << 10) + (units[i + 1] - 0xDC00)
i += 2
else:
i += 1
if 0 <= u <= 0x10FFFF:
out.append(_escape_char(chr(u)))
else:
out.append("?")
return '"' + "".join(out) + '"'
# ---------------------------------------------------------------------------
# printers
# ---------------------------------------------------------------------------
class _Printer(gdb.ValuePrinter):
"""Base class for PIP pretty-printers.
Subclasses implement _total() and _item(i) for element containers.
"""
def __init__(self, val, prefix=""):
self._val = val
self._prefix = prefix
def _total(self):
raise NotImplementedError
def _item(self, i):
raise NotImplementedError
def to_string(self):
try:
total = int(self._total())
if total < 0 or total > _MAX_READ:
return None
if total == 0:
return self._prefix + "{}"
lim = _max_elements()
count = min(total, lim)
items = [self._item(i) for i in range(count)]
tail = "" if total <= lim else ", ... (%d total)" % total
return self._prefix + "{" + ", ".join(items) + tail + "}"
except Exception:
return None
class _PIStringPrinter(_Printer):
"""PIString: PIDeque<PIChar> d + cache members."""
def to_string(self):
try:
d = self._val["d"]
n = int(d["pid_size"])
if n < 0 or n > _MAX_READ:
return None
start = int(d["pid_start"])
data = d["pid_data"]
units = [int(data[start + i]["ch"]) for i in range(n)]
return self._prefix + _utf16_to_string(units)
except Exception:
return None
class _PIByteArrayPrinter(_Printer):
"""PIByteArray: PIDeque<uchar> d. Printed as a hex byte list."""
def _total(self):
return self._val["d"]["pid_size"]
def _item(self, i):
d = self._val["d"]
return "0x%02x" % int(d["pid_data"][int(d["pid_start"]) + i])
class _PICharPrinter(_Printer):
"""PIChar: single UTF-16 code unit (ushort ch)."""
def to_string(self):
try:
ch = int(self._val["ch"])
if 0x20 <= ch <= 0x7E and ch not in (0x22, 0x5C):
return self._prefix + "'%s'" % chr(ch)
return self._prefix + "U+%04x" % ch
except Exception:
return None
class _PIVectorPrinter(_Printer):
"""PIVector<T>: T* piv_data, size_t piv_size, size_t piv_rsize."""
def _total(self):
return self._val["piv_size"]
def _item(self, i):
return str(self._val["piv_data"][i])
class _PIDequePrinter(_Printer):
"""PIDeque<T>: T* pid_data, size_t pid_size, size_t pid_rsize, size_t pid_start.
Also used for PIStringList (derived from PIDeque<PIString>).
"""
def _total(self):
return self._val["pid_size"]
def _item(self, i):
return str(self._val["pid_data"][int(self._val["pid_start"]) + i])
class _PIMapPrinter(_Printer):
"""PIMap<K,V>: PIVector<V> pim_content + PIDeque<MapIndex> pim_index.
MapIndex{ Key key; size_t index; } is kept sorted by key.
"""
def _total(self):
return self._val["pim_index"]["pid_size"]
def _item(self, i):
idx = self._val["pim_index"]
mi = idx["pid_data"][int(idx["pid_start"]) + i]
key = str(mi["key"])
value = str(self._val["pim_content"]["piv_data"][int(mi["index"])])
return "%s: %s" % (key, value)
class _PISetPrinter(_Printer):
"""PISet<T>: derived from PIMap<T,uchar>. Printed as key list."""
def _total(self):
return self._val["pim_index"]["pid_size"]
def _item(self, i):
idx = self._val["pim_index"]
mi = idx["pid_data"][int(idx["pid_start"]) + i]
return str(mi["key"])
class _PIPairPrinter(_Printer):
"""PIPair<K,V>: public first, second members."""
def to_string(self):
try:
return (self._prefix + "{" + str(self._val["first"])
+ ", " + str(self._val["second"]) + "}")
except Exception:
return None
class _PINetworkAddressPrinter(_Printer):
"""PINetworkAddress: union {uint ip_; uchar ip_b[4]}; ushort port_.
The IP bytes are stored in ip_b[0..3] in octet order, so they are
printed as-is (no byte-swap, works for any endianness).
Printed as "i.i.i.i:p".
"""
def to_string(self):
try:
# Mask against a signed char type: octets must be 0..255.
b = [int(self._val["ip_b"][i]) & 0xFF for i in range(4)]
port = int(self._val["port_"])
return self._prefix + "%d.%d.%d.%d:%d" % (b[0], b[1], b[2], b[3], port)
except Exception:
return None
class _PIVariantPrinter(_Printer):
"""PIVariant: PIByteArray _content (serialized), Type _type (enum).
The content is a serialized blob, so the printer shows the type name
and the content size.
"""
def to_string(self):
try:
t = str(self._val["_type"])
if "::" in t:
t = t.split("::", 1)[1]
d = self._val["_content"]["d"]
n = int(d["pid_size"])
return self._prefix + "PIVariant(%s, %d bytes)" % (t, n)
except Exception:
return None
# ---------------------------------------------------------------------------
# lookup and registration
# ---------------------------------------------------------------------------
_PATTERNS = [
(re.compile(r"^PIString$"), lambda v, p: _PIStringPrinter(v, p)),
(re.compile(r"^PIByteArray$"), lambda v, p: _PIByteArrayPrinter(v, p)),
(re.compile(r"^PIChar$"), lambda v, p: _PICharPrinter(v, p)),
(re.compile(r"^PIStringList$"), lambda v, p: _PIDequePrinter(v, p)),
(re.compile(r"^PIDeque<.*>$"), lambda v, p: _PIDequePrinter(v, p)),
(re.compile(r"^PIVector<.*>$"), lambda v, p: _PIVectorPrinter(v, p)),
(re.compile(r"^PIMap<.*>$"), lambda v, p: _PIMapPrinter(v, p)),
(re.compile(r"^PISet<.*>$"), lambda v, p: _PISetPrinter(v, p)),
(re.compile(r"^PIPair<.*>$"), lambda v, p: _PIPairPrinter(v, p)),
(re.compile(r"^PIVariant$"), lambda v, p: _PIVariantPrinter(v, p)),
(re.compile(r"^PINetworkAddress$"), lambda v, p: _PINetworkAddressPrinter(v, p)),
]
def _pip_pp_lookup(val):
"""Pretty-printer lookup function for PIP types.
Handles both values and pointers to values:
p s -> "hello"
p &s -> (PIString *) 0x7fffffffdac0 "hello"
"""
try:
t = val.type.unqualified()
prefix = ""
if t.code == gdb.TYPE_CODE_PTR:
target = t.target()
if target is None:
return None
addr = int(val)
if addr == 0:
return None
prefix = "(%s) 0x%x " % (_c_type_name(t), addr)
val = val.dereference()
t = target.unqualified()
name = t.tag if t.tag is not None else t.name
if name is None:
return None
for pattern, factory in _PATTERNS:
if pattern.match(name):
return factory(val, prefix)
except Exception:
return None
return None
def register_printers(objfile=None):
"""Register PIP pretty printers.
If an objfile is given (or the current objfile is available) the
printers are registered for it (GDB checks the pretty-printer lists
of all objfiles of the current program space when printing values).
Otherwise the printers are registered globally, so they are used
for all values.
Safe to call multiple times.
"""
if objfile is None:
try:
objfile = gdb.current_objfile()
except gdb.error:
objfile = None
if objfile is not None:
try:
if _pip_pp_lookup not in objfile.pretty_printers:
objfile.pretty_printers.append(_pip_pp_lookup)
return
except Exception:
pass
try:
if _pip_pp_lookup not in gdb.pretty_printers:
gdb.pretty_printers.append(_pip_pp_lookup)
except Exception:
pass
# Register for the current objfile when loaded (e.g. `source pip_pp.py`
# in a session with a loaded executable). The auto-load stub calls
# register_printers() explicitly with the objfile of libpip.so.
try:
register_printers()
except Exception:
pass
+74
View File
@@ -0,0 +1,74 @@
// Demo for GDB pretty-printers verification.
// Build with -g, load into GDB, `source ../pip_pp.py`, `break main`, `run`, print globals.
#include "pibytearray.h"
#include "pichar.h"
#include "pimap.h"
#include "pinetworkaddress.h"
#include "pipair.h"
#include "piset.h"
#include "pistring.h"
#include "pivariant.h"
#include "pivector.h"
static PIString str_hello("hello");
static PIString str_cyr(PIString::fromUTF8("\xd0\xbc\xd0\xb8\xd1\x80")); // "мир"
static PIString str_empty;
static PIString str_esc;
static PIByteArray ba_hello(PIByteArray::fromHex("48656c6c6f")); // "Hello"
static PIVector<int> vec_int;
static PIVector<PIString> vec_str;
static PIVector<PIVector<int>> vec_vec;
static PIVector<int> vec_big;
static PIMap<PIString, int> map_str_int;
static PISet<int> set_int;
static PIStringList list_str;
static PIPair<PIString, int> pair_str_int;
static PIVariant var_int;
static PIVariant var_invalid;
static PIString * ptr_str = 0;
static PIString * ptr_null = 0;
static PIChar chr_a;
static PIChar chr_cyr;
static PINetworkAddress addr;
static PINetworkAddress addr_null;
namespace {
struct PPInit {
PPInit() {
vec_int << 1 << 2 << 3;
vec_str << "a" << "bb";
PIVector<int> inner;
inner << 10;
vec_vec << inner << vec_int;
for (int i = 0; i < 40; ++i) {
vec_big << i;
}
map_str_int["x"] = 1;
map_str_int["y"] = 2;
set_int << 5 << 3;
list_str << "one" << "two";
pair_str_int = PIPair<PIString, int>("k", 7);
var_int = PIVariant(42);
var_invalid = PIVariant();
ptr_str = &str_hello;
chr_a = PIChar('a');
chr_cyr = PIChar((char16_t)0x043C);
addr = PINetworkAddress(PIString("192.168.1.10"), 8080);
str_esc = "a\"b\\c\nd\te";
}
};
static PPInit pp_init;
} // namespace
int main() {
return 0;
}
+116
View File
@@ -0,0 +1,116 @@
#!/usr/bin/env bash
#
# Verification for the PIP GDB pretty-printers (tools/gdb/pip_pp.py).
#
# Builds demo_pp.cpp with debug info against an already-built PIP library,
# runs it under GDB in batch mode with the pretty-printers loaded, and
# checks that the expected pretty output is produced.
#
# Usage:
# tools/gdb/test/test_pp.sh <pip-build-dir>
#
# <pip-build-dir> must contain libpip.so and pip_export.h, e.g. build_linux.
# Requires g++ and gdb.
#
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PIP_ROOT="$(cd "${SCRIPT_DIR}/../../.." && pwd)"
BUILD_DIR="${1:-}"
if [[ -z "${BUILD_DIR}" ]]; then
echo "usage: $0 <pip-build-dir> (e.g. build_linux)" >&2
exit 2
fi
BUILD_DIR="$(cd "${BUILD_DIR}" && pwd)"
if [[ ! -f "${BUILD_DIR}/libpip.so" ]]; then
echo "error: ${BUILD_DIR}/libpip.so not found" >&2
exit 2
fi
if [[ ! -f "${BUILD_DIR}/pip_export.h" ]]; then
echo "error: ${BUILD_DIR}/pip_export.h not found (is this a PIP build dir?)" >&2
exit 2
fi
WORK="$(mktemp -d)"
trap 'rm -rf "${WORK}"' EXIT
# Include dirs: libs/main plus each of its subdirectories, plus the build dir.
INCS="-I${PIP_ROOT}/libs/main -I${BUILD_DIR}"
for d in "${PIP_ROOT}"/libs/main/*/; do
INCS+=" -I${d%/}"
done
echo "== building demo =="
g++ -g -O0 -std=c++11 "${SCRIPT_DIR}/demo_pp.cpp" \
${INCS} \
-L"${BUILD_DIR}" -lpip -Wl,-rpath,"${BUILD_DIR}" \
-o "${WORK}/demo_pp"
echo "== running gdb =="
OUT="$(gdb -batch -nx \
-ex "source ${PIP_ROOT}/tools/gdb/pip_pp.py" \
-ex "break main" -ex run \
-ex "print str_hello" \
-ex "print str_cyr" \
-ex "print str_empty" \
-ex "print str_esc" \
-ex "print chr_a" \
-ex "print chr_cyr" \
-ex "print ba_hello" \
-ex "print vec_int" \
-ex "print vec_str" \
-ex "print vec_vec" \
-ex "print vec_big" \
-ex "print map_str_int" \
-ex "print set_int" \
-ex "print list_str" \
-ex "print pair_str_int" \
-ex "print var_int" \
-ex "print var_invalid" \
-ex "print addr" \
-ex "print addr_null" \
-ex "print ptr_str" \
-ex "print *ptr_str" \
"${WORK}/demo_pp" 2>&1)"
echo "${OUT}" | grep -E '^\$[0-9]+ = ' || true
fail=0
check() {
local pattern="$1" desc="$2"
if grep -qE -- "${pattern}" <<<"${OUT}"; then
echo "ok ${desc}"
else
echo "FAIL ${desc} (expected to match: ${pattern})"
fail=1
fi
}
check '^\$[0-9]+ = "hello"$' "PIString ascii"
check '^\$[0-9]+ = "мир"$' "PIString utf-8"
check '^\$[0-9]+ = ""$' "PIString empty"
check '^\$[0-9]+ = "a\\"b\\\\c\\nd\\te"$' "PIString escapes"
check '^\$[0-9]+ = '"'"'a'"'"'$' "PIChar printable"
check '^\$[0-9]+ = U\+043c$' "PIChar non-ascii"
check '^\$[0-9]+ = \{0x48, 0x65, 0x6c, 0x6c, 0x6f\}$' "PIByteArray hex"
check '^\$[0-9]+ = \{1, 2, 3\}$' "PIVector<int>"
check '^\$[0-9]+ = \{"a", "bb"\}$' "PIVector<PIString>"
check '^\$[0-9]+ = \{\{10\}, \{1, 2, 3\}\}$' "PIVector<PIVector<int>>"
check '\.\.\. \(40 total\)\}$' "element count limit"
check '^\$[0-9]+ = \{"x": 1, "y": 2\}$' "PIMap"
check '^\$[0-9]+ = \{3, 5\}$' "PISet"
check '^\$[0-9]+ = \{"one", "two"\}$' "PIStringList"
check '^\$[0-9]+ = \{"k", 7\}$' "PIPair"
check '^\$[0-9]+ = PIVariant\(pivInt, 4 bytes\)$' "PIVariant typed"
check '^\$[0-9]+ = PIVariant\(pivInvalid, 0 bytes\)$' "PIVariant invalid"
check '^\$[0-9]+ = 192\.168\.1\.10:8080$' "PINetworkAddress"
check '^\$[0-9]+ = 0\.0\.0\.0:0$' "PINetworkAddress null"
check '^\$[0-9]+ = \(PIString \*\) 0x[0-9a-f]+ "hello"$' "pointer to PIString"
check '^\$[0-9]+ = "hello"$' "dereferenced pointer"
if [[ "${fail}" -ne 0 ]]; then
echo "== FAILED =="
exit 1
fi
echo "== all checks passed =="
+286
View File
@@ -0,0 +1,286 @@
# Data dumpers for the PIP (Platform-Independent Primitives) library.
#
# Makes the Qt Creator Variables/Expressions views show the content of the
# basic PIP types instead of their raw internals (the Qt Creator dumper
# does not use GDB pretty-printers, it needs its own dumpers; this module
# mirrors tools/gdb/pip_pp.py):
#
# PIString -> hello
# PIByteArray -> {0x48, 0x65, 0x6c, 0x6c, 0x6f}
# PIChar -> 'a' or U+043m
# PIVector<T> -> [1, 2, 3]
# PIDeque<T> -> [1, 2, 3]
# PIStringList -> ["one", "two"]
# PIMap<K, V> -> {k: v}
# PISet<T> -> [3, 5]
# PIPair<K, V> -> (k, v)
# PIVariant -> PIVariant(pivInt, 4 bytes)
# PINetworkAddress -> 192.168.1.10:8080
#
# Enable: Qt Creator -> Settings -> Debugger -> "Variables and expressions"
# tab -> "Extra dumper file" -> point to this file.
#
# Requirements: the executable and the PIP library are built with debug
# information (-g). All dumpers are read-only and defensive: on any error
# they fall back to the default (raw) member expansion.
from dumper import Children, SubItem
# Maximum number of elements printed per container (matches pip_pp.py).
_MAX_ELEMENTS = 32
# Absolute safety cap: never try to read more elements than this
# (protects against corrupted size fields).
_MAX_READ = 10000000
# Maximum string/bytearray elements read for the display value.
_MAX_DISPLAY_UNITS = 4096
def _utf16Text(units):
"""Build a plain text string from a list of UTF-16 code units."""
out = []
i = 0
n = len(units)
while i < n:
u = units[i]
# Combine surrogate pairs.
if 0xD800 <= u <= 0xDBFF and i + 1 < n and 0xDC00 <= units[i + 1] <= 0xDFFF:
u = 0x10000 + ((u - 0xD800) << 10) + (units[i + 1] - 0xDC00)
i += 2
else:
i += 1
if 0 <= u <= 0x10FFFF:
out.append(chr(u))
else:
out.append("\ufffd")
return "".join(out)
def _dequeUnits(dvalue):
"""Read UTF-16 units from a PIDeque<PIChar> value, or None on error."""
size = int(dvalue['pid_size'])
if size < 0 or size > _MAX_READ:
return None
if size == 0:
return []
data = dvalue['pid_data']
start = int(dvalue['pid_start'])
if start < 0:
return None
return [int(data[start + i]['ch']) for i in range(min(size, _MAX_DISPLAY_UNITS))]
def qdump__PIString(d, value):
"""PIString: PIDeque<PIChar> d + cache members. Printed as plain text."""
try:
size = int(value['d']['pid_size'])
units = _dequeUnits(value['d'])
if units is not None:
text = _utf16Text(units)
if size > _MAX_DISPLAY_UNITS:
text += "..."
# Hex-encode with the utf8 tag so the raw text (quotes,
# backslashes, control chars) never appears unescaped in the
# dumper protocol stream.
d.putValue(d.hexencode(text), "utf8", length=len(text))
except Exception:
pass
d.putPlainChildren(value)
def qdump__PIByteArray(d, value):
"""PIByteArray: PIDeque<uchar> d. Printed as a hex byte list."""
try:
size = int(value['d']['pid_size'])
if 0 <= size <= _MAX_READ:
if size == 0:
d.putValue('{}')
else:
dvalue = value['d']
data = dvalue['pid_data']
start = int(dvalue['pid_start'])
lim = min(size, _MAX_ELEMENTS)
items = ['0x%02x' % int(data[start + i]) for i in range(lim)]
tail = '' if size <= _MAX_ELEMENTS else ', ... (%d total)' % size
d.putValue('{%s%s}' % (', '.join(items), tail))
except Exception:
pass
d.putPlainChildren(value)
def qdump__PIChar(d, value):
"""PIChar: single UTF-16 code unit (ushort ch)."""
try:
ch = int(value['ch'])
if 0x20 <= ch <= 0x7E and ch not in (0x22, 0x5C):
d.putValue("'%s'" % chr(ch))
else:
d.putValue('U+%04x' % ch)
except Exception:
pass
d.putPlainChildren(value)
def qdump__PINetworkAddress(d, value):
"""PINetworkAddress: union {uint ip_; uchar ip_b[4]}; ushort port_.
The IP bytes are stored in ip_b[0..3] in octet order, so they are
printed as-is (no byte-swap, works for any endianness).
Printed as "i.i.i.i:p".
"""
try:
try:
# Mask against a signed char type: octets must be 0..255.
b = [int(value['ip_b'][i]) & 0xFF for i in range(4)]
except Exception:
ip = int(value['ip_'])
if d.byteorder == 'big':
b = [(ip >> 24) & 0xFF, (ip >> 16) & 0xFF,
(ip >> 8) & 0xFF, ip & 0xFF]
else:
b = [ip & 0xFF, (ip >> 8) & 0xFF,
(ip >> 16) & 0xFF, (ip >> 24) & 0xFF]
port = int(value['port_'])
d.putValue('%d.%d.%d.%d:%d' % (b[0], b[1], b[2], b[3], port))
except Exception:
pass
d.putPlainChildren(value)
def qdump__PIVector(d, value):
"""PIVector<T>: T* piv_data, size_t piv_size, size_t piv_rsize."""
try:
inner = value.type[0]
size = int(value['piv_size'])
if 0 <= size <= _MAX_READ:
d.putItemCount(size)
d.putPlotData(int(value['piv_data']), size, inner)
return
except Exception:
pass
d.putPlainChildren(value)
def _dequeDump(d, value, inner):
"""Dump a PIDeque<T>-layout value with the given element type."""
size = int(value['pid_size'])
start = int(value['pid_start'])
if 0 <= size <= _MAX_READ and 0 <= start:
d.putItemCount(size)
d.putPlotData(int(value['pid_data']) + start * inner.size(), size, inner)
return True
return False
def qdump__PIDeque(d, value):
"""PIDeque<T>: T* pid_data, size_t pid_size, size_t pid_rsize, size_t pid_start."""
try:
if _dequeDump(d, value, value.type[0]):
return
except Exception:
pass
d.putPlainChildren(value)
def qdump__PIStringList(d, value):
"""PIStringList: derived from PIDeque<PIString>."""
try:
if _dequeDump(d, value, d.createType('PIString')):
return
except Exception:
pass
d.putPlainChildren(value)
def qdump__PIMap(d, value):
"""PIMap<K, V>: PIVector<V> pim_content + PIDeque<MapIndex> pim_index."""
try:
index = value['pim_index']
size = int(index['pid_size'])
if not (0 <= size <= _MAX_READ):
raise ValueError('bad size')
d.putItemCount(size)
if d.isExpanded():
start = int(index['pid_start'])
entryType = index.type[0]
entrySize = entryType.size()
indexBase = int(index['pid_data'])
content = value['pim_content']
valueType = content.type[0]
valueBase = int(content['piv_data'])
with Children(d, size, maxNumChild=1000):
for i in d.childRange():
entry = d.createValueFromAddress(
indexBase + (start + i) * entrySize, entryType)
key = entry['key']
val = d.createValueFromAddress(
valueBase + int(entry['index']) * valueType.size(),
valueType)
d.putPairItem(i, (key, val), 'key', 'value')
return
except Exception:
pass
d.putPlainChildren(value)
def qdump__PISet(d, value):
"""PISet<T>: derived from PIMap<T, uchar>. Printed as the key list."""
try:
index = value['pim_index']
size = int(index['pid_size'])
if not (0 <= size <= _MAX_READ):
raise ValueError('bad size')
d.putItemCount(size)
if d.isExpanded():
start = int(index['pid_start'])
entryType = index.type[0]
entrySize = entryType.size()
indexBase = int(index['pid_data'])
with Children(d, size, maxNumChild=1000):
for i in d.childRange():
entry = d.createValueFromAddress(
indexBase + (start + i) * entrySize, entryType)
d.putSubItem(i, entry['key'])
return
except Exception:
pass
d.putPlainChildren(value)
def qdump__PIPair(d, value):
"""PIPair<K, V>: public first, second members."""
try:
with Children(d):
first = d.putSubItem('first', value['first'])
second = d.putSubItem('second', value['second'])
d.putValue('(%s, %s)' % (
first.value if first.encoding is None else '...',
second.value if second.encoding is None else '...'))
except Exception:
d.putPlainChildren(value)
def _enumName(d, value):
"""Name of an enum value (e.g. 'pivInt'), or its number on failure."""
try:
import gdb
ival = int(value)
etype = gdb.lookup_type(d.type_name(value.typeid))
if etype.code != gdb.TYPE_CODE_ENUM:
return str(ival)
name = str(gdb.Value(ival).cast(etype))
return name.split('::')[-1] if '::' in name else name
except Exception:
try:
return str(int(value))
except Exception:
return '?'
def qdump__PIVariant(d, value):
"""PIVariant: PIByteArray _content (serialized) + Type _type (enum)."""
try:
nbytes = int(value['_content']['d']['pid_size'])
d.putValue('PIVariant(%s, %d bytes)' % (_enumName(d, value['_type']), nbytes))
except Exception:
pass
d.putPlainChildren(value)