Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions Doc/howto/gdb_helpers.rst
Original file line number Diff line number Diff line change
Expand Up @@ -352,9 +352,21 @@ Again, the implementation details can be revealed with a cast to
builtin 'len' = <built-in function len>
(gdb) py-print scarlet_pimpernel
'scarlet_pimpernel' not found
(gdb) py-print nested_dict
local 'nested_dict' = {'a': {'b': {'c': {'d': 1, 'e': 2, 'f': 3}, 'g': {'h': 4, 'i': 5}}, 'j': 6, 'k': {'l': 7, 'm': {'n': 8, 'o': 9}}}, 'p': {'q': {'r': 10, 's': {'t': 11, 'u': {'v': 12, 'w': 13, 'x': 14}}}}, 'y': {'z': 15}}
(gdb) set py-verbose-print on
(gdb) py-print nested_dict
local 'nested_dict' = \
{'a': {'b': {'c': {'d': 1, 'e': 2, 'f': 3}, 'g': {'h': 4, 'i': 5}},
'j': 6,
'k': {'l': 7, 'm': {'n': 8, 'o': 9}}},
'p': {'q': {'r': 10, 's': {'t': 11, 'u': {'v': 12, 'w': 13, 'x': 14}}}},
'y': {'z': 15}}

If the current C frame corresponds to multiple Python frames, ``py-print``
only considers the first one.
Setting the parameter ``py-verbose-print`` to ``on`` enables Python object pretty printing
and allow Python objects to be printed completely instead of being truncated at some limit.

``py-locals``
-------------
Expand Down
28 changes: 25 additions & 3 deletions Lib/test/test_gdb/test_misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,12 @@ def test_two_abs_args(self):
from _testcapi import pyobject_vectorcall

def foo(a, b, c):
nested_dict = \
{'a': {'b': {'c': {'d': 1, 'e': 2, 'f': 3}, 'g': {'h': 4, 'i': 5}},
'j': 6,
'k': {'l': 7, 'm': {'n': 8, 'o': 9}}},
'p': {'q': {'r': 10, 's': {'t': 11, 'u': {'v': 12, 'w': 13, 'x': 14}}}},
'y': {'z': 15}}
bar(a, b, c)

def bar(a, b, c):
Expand All @@ -94,7 +100,7 @@ def test_pyup_command(self):
cmds_after_breakpoint=['py-up', 'py-up'])
self.assertMultilineMatches(bt,
r'''^.*
#[0-9]+ Frame 0x-?[0-9a-f]+, for file <string>, line 12, in baz \(args=\(1, 2, 3\)\)
#[0-9]+ Frame 0x-?[0-9a-f]+, for file <string>, line 17, in baz \(args=\(1, 2, 3\)\)
#[0-9]+ <built-in method pyobject_vectorcall of module object at remote 0x[0-9a-f]+>
$''')

Expand Down Expand Up @@ -123,9 +129,9 @@ def test_up_then_down(self):
cmds_after_breakpoint=['py-up', 'py-up', 'py-down'])
self.assertMultilineMatches(bt,
r'''^.*
#[0-9]+ Frame 0x-?[0-9a-f]+, for file <string>, line 12, in baz \(args=\(1, 2, 3\)\)
#[0-9]+ Frame 0x-?[0-9a-f]+, for file <string>, line 17, in baz \(args=\(1, 2, 3\)\)
#[0-9]+ <built-in method pyobject_vectorcall of module object at remote 0x[0-9a-f]+>
#[0-9]+ Frame 0x-?[0-9a-f]+, for file <string>, line 12, in baz \(args=\(1, 2, 3\)\)
#[0-9]+ Frame 0x-?[0-9a-f]+, for file <string>, line 17, in baz \(args=\(1, 2, 3\)\)
$''')

class PyPrintTests(DebuggerTests):
Expand Down Expand Up @@ -163,6 +169,22 @@ def test_printing_builtin(self):
self.assertMultilineMatches(bt,
r".*\nbuiltin 'len' = <built-in method len of module object at remote 0x-?[0-9a-f]+>\n.*")

@unittest.skipIf(python_is_optimized(),
"Python was compiled with optimizations")
def test_pretty_printing(self):
'Verify that the "py-print" with pretty printing command works'
bt = self.get_stack_trace(source=SAMPLE_WITH_C_CALL,
cmds_after_breakpoint=['py-up', 'py-up', 'set py-verbose-print on', 'py-print nested_dict'])
self.assertMultilineMatches(bt,
r'''.*^
local 'nested_dict' = \\
{'a': {'b': {'c': {'d': 1, 'e': 2, 'f': 3}, 'g': {'h': 4, 'i': 5}},
'j': 6,
'k': {'l': 7, 'm': {'n': 8, 'o': 9}}},
'p': {'q': {'r': 10, 's': {'t': 11, 'u': {'v': 12, 'w': 13, 'x': 14}}}},
'y': {'z': 15}}.*$
''')

class PyLocalsTests(DebuggerTests):
@unittest.skipIf(python_is_optimized(),
"Python was compiled with optimizations")
Expand Down
15 changes: 11 additions & 4 deletions Lib/test/test_gdb/test_pretty_print.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ def setUpModule():
class PrettyPrintTests(DebuggerTests):
def get_gdb_repr(self, source,
cmds_after_breakpoint=None,
import_site=False):
import_site=False,
verbose_output=False):
# Given an input python source representation of data,
# run "python -c'id(DATA)'" under gdb with a breakpoint on
# builtin_id and scrape out gdb's representation of the "op"
Expand All @@ -31,7 +32,8 @@ def get_gdb_repr(self, source,
cmds_after_breakpoint = cmds_after_breakpoint or ["backtrace 1"]
gdb_output = self.get_stack_trace(source, breakpoint=BREAKPOINT_FN,
cmds_after_breakpoint=cmds_after_breakpoint,
import_site=import_site)
import_site=import_site,
verbose_output=verbose_output)
# gdb can insert additional '\n' and space characters in various places
# in its output, depending on the width of the terminal it's connected
# to (using its "wrap_here" function)
Expand All @@ -52,10 +54,10 @@ def test_getting_backtrace(self):
gdb_output = self.get_stack_trace('id(42)')
self.assertTrue(BREAKPOINT_FN in gdb_output)

def assertGdbRepr(self, val, exp_repr=None):
def assertGdbRepr(self, val, exp_repr=None, verbose_output=False):
# Ensure that gdb's rendering of the value in a debugged process
# matches repr(value) in this process:
gdb_repr, gdb_output = self.get_gdb_repr('id(' + ascii(val) + ')')
gdb_repr, gdb_output = self.get_gdb_repr('id(' + ascii(val) + ')', verbose_output=verbose_output)
if not exp_repr:
exp_repr = repr(val)
self.assertEqual(gdb_repr, exp_repr,
Expand Down Expand Up @@ -414,6 +416,11 @@ def test_truncation(self):
self.assertEqual(len(gdb_repr),
1024 + len('...(truncated)'))

def test_verbose_not_truncated(self):
'Verify that very long output is not truncated when verbose output is requested'
long_list = [42] * 500
self.assertGdbRepr(long_list, verbose_output=True)

def test_builtin_method(self):
gdb_repr, gdb_output = self.get_gdb_repr('import sys; id(sys.stdout.readlines)')
self.assertTrue(re.match(r'<built-in method readlines of _io.TextIOWrapper object at remote 0x-?[0-9a-f]+>',
Expand Down
6 changes: 5 additions & 1 deletion Lib/test/test_gdb/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,8 @@ def get_stack_trace(self, source=None, script=None,
breakpoint=BREAKPOINT_FN,
cmds_after_breakpoint=None,
import_site=False,
ignore_stderr=False):
ignore_stderr=False,
verbose_output=False):
'''
Run 'python -c SOURCE' under gdb with a breakpoint.

Expand Down Expand Up @@ -210,6 +211,9 @@ def get_stack_trace(self, source=None, script=None,
if GDB_VERSION >= (7, 4):
commands += ['set print entry-values no']

if verbose_output:
commands += ['set py-verbose-print on']

if cmds_after_breakpoint:
if CET_PROTECTION:
# bpo-32962: When Python is compiled with -mcet
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Allow switching to pretty-printer when using gdb via ``set py-verbose-print on``
62 changes: 42 additions & 20 deletions Tools/gdb/libpython.py
Original file line number Diff line number Diff line change
Expand Up @@ -1580,31 +1580,30 @@ def int_from_int(gdbval):
return int(gdbval)


def stringify(val):
# TODO: repr() puts everything on one line; pformat can be nicer, but
# can lead to v.long results; this function isolates the choice
if True:
return repr(val)
_verbose_stringify = False

def stringify(pyop, multiline=False):
if _verbose_stringify:
# Generate full proxy value then stringify it.
# Doing so could be expensive
proxyval = pyop.proxyval(set())
if multiline:
from pprint import pformat
return pformat(proxyval)
else:
return repr(proxyval)
else:
from pprint import pformat
return pformat(val)
return pyop.get_truncated_repr(MAX_OUTPUT_LEN)


class PyObjectPtrPrinter:
"Prints a (PyObject*)"

def __init__ (self, gdbval):
def __init__(self, gdbval):
self.gdbval = gdbval

def to_string (self):
pyop = PyObjectPtr.from_pyobject_ptr(self.gdbval)
if True:
return pyop.get_truncated_repr(MAX_OUTPUT_LEN)
else:
# Generate full proxy value then stringify it.
# Doing so could be expensive
proxyval = pyop.proxyval(set())
return stringify(proxyval)
def to_string(self):
return stringify(PyObjectPtr.from_pyobject_ptr(self.gdbval))

def pretty_printer_lookup(gdbval):
type = gdbval.type.strip_typedefs().unqualified()
Expand All @@ -1624,8 +1623,9 @@ def pretty_printer_lookup(gdbval):
(gdb) python

import sys
sys.path.append('/home/david/coding/python-gdb')
sys.path.append('/home/david/coding/cpython/Tools/gdb')
import libpython
from importlib import reload
end

then reloading it after each edit like this:
Expand Down Expand Up @@ -2087,6 +2087,23 @@ def invoke(self, args, from_tty):

PyBacktrace()

class PyParameterVerbosePrint(gdb.Parameter):
set_doc ="Enable Python object pretty printing and allow Python objects to be printed completely instead of being truncated at some limit"

def __init__(self):
gdb.Parameter.__init__ (self,
"py-verbose-print",
gdb.COMMAND_DATA,
gdb.PARAM_BOOLEAN)
self.value = False

def get_set_string (self):
global _verbose_stringify
_verbose_stringify = self.value
return ""

PyParameterVerbosePrint()

class PyPrint(gdb.Command):
'Look up the given python variable name, and print it'
def __init__(self):
Expand All @@ -2111,11 +2128,16 @@ def invoke(self, args, from_tty):

pyop_var, scope = pyop_frame.get_var_by_name(name)

var_stringifed = stringify(pyop_var, multiline=True)
multiline = "\n" in var_stringifed
if multiline:
# Prefix with a line continuation and indent by single space
var_stringifed = "\\\n %s" % var_stringifed.replace("\n", "\n ")
if pyop_var:
print('%s %r = %s'
% (scope,
name,
pyop_var.get_truncated_repr(MAX_OUTPUT_LEN)))
var_stringifed))
else:
print('%r not found' % name)

Expand Down Expand Up @@ -2151,7 +2173,7 @@ def invoke(self, args, from_tty):
for pyop_name, pyop_value in pyop_frame.iter_locals():
print('%s = %s'
% (pyop_name.proxyval(set()),
pyop_value.get_truncated_repr(MAX_OUTPUT_LEN)))
stringify(pyop_value)))


pyop_frame = pyop_frame.previous()
Expand Down
Loading