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:
@@ -8,3 +8,4 @@ CMakeLists.txt.user*
|
|||||||
/build*
|
/build*
|
||||||
/AGENTS.md
|
/AGENTS.md
|
||||||
/plans
|
/plans
|
||||||
|
__pycache__
|
||||||
|
|||||||
+27
-1
@@ -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)
|
||||||
|
|||||||
@@ -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
@@ -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.
|
||||||
|
|||||||
@@ -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())
|
||||||
@@ -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
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
Executable
+116
@@ -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 =="
|
||||||
@@ -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)
|
||||||
Reference in New Issue
Block a user