376 lines
11 KiB
Python
376 lines
11 KiB
Python
"""
|
|
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
|