Skip to content

Commit 54c52d4

Browse files
committed
gh-129351: Allow switching to pretty-printer when using gdb
1 parent 95d6e0b commit 54c52d4

File tree

6 files changed

+96
-28
lines changed

6 files changed

+96
-28
lines changed

Doc/howto/gdb_helpers.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,9 +352,21 @@ Again, the implementation details can be revealed with a cast to
352352
builtin 'len' = <built-in function len>
353353
(gdb) py-print scarlet_pimpernel
354354
'scarlet_pimpernel' not found
355+
(gdb) py-print nested_dict
356+
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}}
357+
(gdb) set py-verbose-print on
358+
(gdb) py-print nested_dict
359+
local 'nested_dict' = \
360+
{'a': {'b': {'c': {'d': 1, 'e': 2, 'f': 3}, 'g': {'h': 4, 'i': 5}},
361+
'j': 6,
362+
'k': {'l': 7, 'm': {'n': 8, 'o': 9}}},
363+
'p': {'q': {'r': 10, 's': {'t': 11, 'u': {'v': 12, 'w': 13, 'x': 14}}}},
364+
'y': {'z': 15}}
355365

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

359371
``py-locals``
360372
-------------

Lib/test/test_gdb/test_misc.py

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,12 @@ def test_two_abs_args(self):
7171
from _testcapi import pyobject_vectorcall
7272
7373
def foo(a, b, c):
74+
nested_dict = \
75+
{'a': {'b': {'c': {'d': 1, 'e': 2, 'f': 3}, 'g': {'h': 4, 'i': 5}},
76+
'j': 6,
77+
'k': {'l': 7, 'm': {'n': 8, 'o': 9}}},
78+
'p': {'q': {'r': 10, 's': {'t': 11, 'u': {'v': 12, 'w': 13, 'x': 14}}}},
79+
'y': {'z': 15}}
7480
bar(a, b, c)
7581
7682
def bar(a, b, c):
@@ -94,7 +100,7 @@ def test_pyup_command(self):
94100
cmds_after_breakpoint=['py-up', 'py-up'])
95101
self.assertMultilineMatches(bt,
96102
r'''^.*
97-
#[0-9]+ Frame 0x-?[0-9a-f]+, for file <string>, line 12, in baz \(args=\(1, 2, 3\)\)
103+
#[0-9]+ Frame 0x-?[0-9a-f]+, for file <string>, line 17, in baz \(args=\(1, 2, 3\)\)
98104
#[0-9]+ <built-in method pyobject_vectorcall of module object at remote 0x[0-9a-f]+>
99105
$''')
100106

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

131137
class PyPrintTests(DebuggerTests):
@@ -162,6 +168,22 @@ def test_printing_builtin(self):
162168
cmds_after_breakpoint=['py-up', 'py-print len'])
163169
self.assertMultilineMatches(bt,
164170
r".*\nbuiltin 'len' = <built-in method len of module object at remote 0x-?[0-9a-f]+>\n.*")
171+
172+
@unittest.skipIf(python_is_optimized(),
173+
"Python was compiled with optimizations")
174+
def test_pretty_printing(self):
175+
'Verify that the "py-print" with pretty printing command works'
176+
bt = self.get_stack_trace(source=SAMPLE_WITH_C_CALL,
177+
cmds_after_breakpoint=['py-up', 'py-up', 'set py-verbose-print on', 'py-print nested_dict'])
178+
self.assertMultilineMatches(bt,
179+
r'''.*^
180+
local 'nested_dict' = \\
181+
{'a': {'b': {'c': {'d': 1, 'e': 2, 'f': 3}, 'g': {'h': 4, 'i': 5}},
182+
'j': 6,
183+
'k': {'l': 7, 'm': {'n': 8, 'o': 9}}},
184+
'p': {'q': {'r': 10, 's': {'t': 11, 'u': {'v': 12, 'w': 13, 'x': 14}}}},
185+
'y': {'z': 15}}.*$
186+
''')
165187

166188
class PyLocalsTests(DebuggerTests):
167189
@unittest.skipIf(python_is_optimized(),

Lib/test/test_gdb/test_pretty_print.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ def setUpModule():
1414
class PrettyPrintTests(DebuggerTests):
1515
def get_gdb_repr(self, source,
1616
cmds_after_breakpoint=None,
17-
import_site=False):
17+
import_site=False,
18+
verbose_output=False):
1819
# Given an input python source representation of data,
1920
# run "python -c'id(DATA)'" under gdb with a breakpoint on
2021
# builtin_id and scrape out gdb's representation of the "op"
@@ -31,7 +32,8 @@ def get_gdb_repr(self, source,
3132
cmds_after_breakpoint = cmds_after_breakpoint or ["backtrace 1"]
3233
gdb_output = self.get_stack_trace(source, breakpoint=BREAKPOINT_FN,
3334
cmds_after_breakpoint=cmds_after_breakpoint,
34-
import_site=import_site)
35+
import_site=import_site,
36+
verbose_output=verbose_output)
3537
# gdb can insert additional '\n' and space characters in various places
3638
# in its output, depending on the width of the terminal it's connected
3739
# to (using its "wrap_here" function)
@@ -52,10 +54,10 @@ def test_getting_backtrace(self):
5254
gdb_output = self.get_stack_trace('id(42)')
5355
self.assertTrue(BREAKPOINT_FN in gdb_output)
5456

55-
def assertGdbRepr(self, val, exp_repr=None):
57+
def assertGdbRepr(self, val, exp_repr=None, verbose_output=False):
5658
# Ensure that gdb's rendering of the value in a debugged process
5759
# matches repr(value) in this process:
58-
gdb_repr, gdb_output = self.get_gdb_repr('id(' + ascii(val) + ')')
60+
gdb_repr, gdb_output = self.get_gdb_repr('id(' + ascii(val) + ')', verbose_output=verbose_output)
5961
if not exp_repr:
6062
exp_repr = repr(val)
6163
self.assertEqual(gdb_repr, exp_repr,
@@ -414,6 +416,11 @@ def test_truncation(self):
414416
self.assertEqual(len(gdb_repr),
415417
1024 + len('...(truncated)'))
416418

419+
def test_verbose_not_truncated(self):
420+
'Verify that very long output is not truncated when verbose output is requested'
421+
long_list = [42] * 500
422+
self.assertGdbRepr(long_list, verbose_output=True)
423+
417424
def test_builtin_method(self):
418425
gdb_repr, gdb_output = self.get_gdb_repr('import sys; id(sys.stdout.readlines)')
419426
self.assertTrue(re.match(r'<built-in method readlines of _io.TextIOWrapper object at remote 0x-?[0-9a-f]+>',

Lib/test/test_gdb/util.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,8 @@ def get_stack_trace(self, source=None, script=None,
160160
breakpoint=BREAKPOINT_FN,
161161
cmds_after_breakpoint=None,
162162
import_site=False,
163-
ignore_stderr=False):
163+
ignore_stderr=False,
164+
verbose_output=False):
164165
'''
165166
Run 'python -c SOURCE' under gdb with a breakpoint.
166167
@@ -209,6 +210,9 @@ def get_stack_trace(self, source=None, script=None,
209210
# Disable this:
210211
if GDB_VERSION >= (7, 4):
211212
commands += ['set print entry-values no']
213+
214+
if verbose_output:
215+
commands += ['set py-verbose-print on']
212216

213217
if cmds_after_breakpoint:
214218
if CET_PROTECTION:
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Allow switching to pretty-printer when using gdb via ``set py-verbose-print on``

Tools/gdb/libpython.py

Lines changed: 42 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1580,31 +1580,30 @@ def int_from_int(gdbval):
15801580
return int(gdbval)
15811581

15821582

1583-
def stringify(val):
1584-
# TODO: repr() puts everything on one line; pformat can be nicer, but
1585-
# can lead to v.long results; this function isolates the choice
1586-
if True:
1587-
return repr(val)
1583+
_verbose_stringify = False
1584+
1585+
def stringify(pyop, multiline=False):
1586+
if _verbose_stringify:
1587+
# Generate full proxy value then stringify it.
1588+
# Doing so could be expensive
1589+
proxyval = pyop.proxyval(set())
1590+
if multiline:
1591+
from pprint import pformat
1592+
return pformat(proxyval)
1593+
else:
1594+
return repr(proxyval)
15881595
else:
1589-
from pprint import pformat
1590-
return pformat(val)
1596+
return pyop.get_truncated_repr(MAX_OUTPUT_LEN)
15911597

15921598

15931599
class PyObjectPtrPrinter:
15941600
"Prints a (PyObject*)"
15951601

1596-
def __init__ (self, gdbval):
1602+
def __init__(self, gdbval):
15971603
self.gdbval = gdbval
15981604

1599-
def to_string (self):
1600-
pyop = PyObjectPtr.from_pyobject_ptr(self.gdbval)
1601-
if True:
1602-
return pyop.get_truncated_repr(MAX_OUTPUT_LEN)
1603-
else:
1604-
# Generate full proxy value then stringify it.
1605-
# Doing so could be expensive
1606-
proxyval = pyop.proxyval(set())
1607-
return stringify(proxyval)
1605+
def to_string(self):
1606+
return stringify(PyObjectPtr.from_pyobject_ptr(self.gdbval))
16081607

16091608
def pretty_printer_lookup(gdbval):
16101609
type = gdbval.type.strip_typedefs().unqualified()
@@ -1624,8 +1623,9 @@ def pretty_printer_lookup(gdbval):
16241623
(gdb) python
16251624
16261625
import sys
1627-
sys.path.append('/home/david/coding/python-gdb')
1626+
sys.path.append('/home/david/coding/cpython/Tools/gdb')
16281627
import libpython
1628+
from importlib import reload
16291629
end
16301630
16311631
then reloading it after each edit like this:
@@ -2087,6 +2087,23 @@ def invoke(self, args, from_tty):
20872087

20882088
PyBacktrace()
20892089

2090+
class PyParameterVerbosePrint(gdb.Parameter):
2091+
set_doc ="Enable Python object pretty printing and allow Python objects to be printed completely instead of being truncated at some limit"
2092+
2093+
def __init__(self):
2094+
gdb.Parameter.__init__ (self,
2095+
"py-verbose-print",
2096+
gdb.COMMAND_DATA,
2097+
gdb.PARAM_BOOLEAN)
2098+
self.value = False
2099+
2100+
def get_set_string (self):
2101+
global _verbose_stringify
2102+
_verbose_stringify = self.value
2103+
return ""
2104+
2105+
PyParameterVerbosePrint()
2106+
20902107
class PyPrint(gdb.Command):
20912108
'Look up the given python variable name, and print it'
20922109
def __init__(self):
@@ -2111,11 +2128,16 @@ def invoke(self, args, from_tty):
21112128

21122129
pyop_var, scope = pyop_frame.get_var_by_name(name)
21132130

2131+
var_stringifed = stringify(pyop_var, multiline=True)
2132+
multiline = "\n" in var_stringifed
2133+
if multiline:
2134+
# Prefix with a line continuation and indent by single space
2135+
var_stringifed = "\\\n %s" % var_stringifed.replace("\n", "\n ")
21142136
if pyop_var:
21152137
print('%s %r = %s'
21162138
% (scope,
21172139
name,
2118-
pyop_var.get_truncated_repr(MAX_OUTPUT_LEN)))
2140+
var_stringifed))
21192141
else:
21202142
print('%r not found' % name)
21212143

@@ -2151,7 +2173,7 @@ def invoke(self, args, from_tty):
21512173
for pyop_name, pyop_value in pyop_frame.iter_locals():
21522174
print('%s = %s'
21532175
% (pyop_name.proxyval(set()),
2154-
pyop_value.get_truncated_repr(MAX_OUTPUT_LEN)))
2176+
stringify(pyop_value)))
21552177

21562178

21572179
pyop_frame = pyop_frame.previous()

0 commit comments

Comments
 (0)