Skip to content

Commit e36f94f

Browse files
authored
bpo-32962: Backport python-gdb.py and test_gdb.py from master (GH-7726)
* bpo-32962: python-gdb catchs ValueError on read_var() (GH-7692) python-gdb now catchs ValueError on read_var(): when Python has no debug symbols for example. (cherry picked from commit 019d33b) * bpo-32962: python-gdb catchs UnicodeDecodeError (GH-7693) python-gdb now catchs UnicodeDecodeError exceptions when calling string(). (cherry picked from commit d22fc0b) bpo-29367: python-gdb.py now supports also method-wrapper (wrapperobject) objects. (cherry picked from commit 6110833)
1 parent 184e8ed commit e36f94f

File tree

5 files changed

+140
-35
lines changed

5 files changed

+140
-35
lines changed

Lib/test/test_gdb.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,14 @@
33
# The code for testing gdb was adapted from similar work in Unladen Swallow's
44
# Lib/test/test_jit_gdb.py
55

6+
import locale
67
import os
78
import re
89
import subprocess
910
import sys
1011
import sysconfig
12+
import textwrap
1113
import unittest
12-
import sysconfig
1314

1415
from test import test_support
1516
from test.test_support import run_unittest, findfile
@@ -863,7 +864,24 @@ def test_pycfunction(self):
863864
breakpoint='time_gmtime',
864865
cmds_after_breakpoint=['py-bt-full'],
865866
)
866-
self.assertIn('#0 <built-in function gmtime', gdb_output)
867+
self.assertIn('#1 <built-in function gmtime', gdb_output)
868+
869+
@unittest.skipIf(python_is_optimized(),
870+
"Python was compiled with optimizations")
871+
def test_wrapper_call(self):
872+
cmd = textwrap.dedent('''
873+
class MyList(list):
874+
def __init__(self):
875+
super(MyList, self).__init__() # wrapper_call()
876+
877+
print("first break point")
878+
l = MyList()
879+
''')
880+
# Verify with "py-bt":
881+
gdb_output = self.get_stack_trace(cmd,
882+
cmds_after_breakpoint=['break wrapper_call', 'continue', 'py-bt'])
883+
self.assertRegexpMatches(gdb_output,
884+
r"<method-wrapper u?'__init__' of MyList object at ")
867885

868886

869887
class PyPrintTests(DebuggerTests):
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
python-gdb now catchs ValueError on read_var(): when Python has no debug
2+
symbols for example.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
python-gdb now catchs ``UnicodeDecodeError`` exceptions when calling
2+
``string()``.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
python-gdb.py now supports also method-wrapper (wrapperobject) objects.

Tools/gdb/libpython.py

Lines changed: 115 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -241,12 +241,13 @@ def is_optimized_out(self):
241241

242242
def safe_tp_name(self):
243243
try:
244-
return self.type().field('tp_name').string()
245-
except NullPyObjectPtr:
246-
# NULL tp_name?
247-
return 'unknown'
248-
except RuntimeError:
249-
# Can't even read the object at all?
244+
ob_type = self.type()
245+
tp_name = ob_type.field('tp_name')
246+
return tp_name.string()
247+
# NullPyObjectPtr: NULL tp_name?
248+
# RuntimeError: Can't even read the object at all?
249+
# UnicodeDecodeError: Failed to decode tp_name bytestring
250+
except (NullPyObjectPtr, RuntimeError, UnicodeDecodeError):
250251
return 'unknown'
251252

252253
def proxyval(self, visited):
@@ -320,7 +321,9 @@ def subclass_from_type(cls, t):
320321
try:
321322
tp_name = t.field('tp_name').string()
322323
tp_flags = int(t.field('tp_flags'))
323-
except RuntimeError:
324+
# RuntimeError: NULL pointers
325+
# UnicodeDecodeError: string() fails to decode the bytestring
326+
except (RuntimeError, UnicodeDecodeError):
324327
# Handle any kind of error e.g. NULL ptrs by simply using the base
325328
# class
326329
return cls
@@ -336,6 +339,7 @@ def subclass_from_type(cls, t):
336339
'set' : PySetObjectPtr,
337340
'frozenset' : PySetObjectPtr,
338341
'builtin_function_or_method' : PyCFunctionObjectPtr,
342+
'method-wrapper': wrapperobject,
339343
}
340344
if tp_name in name_map:
341345
return name_map[tp_name]
@@ -602,7 +606,10 @@ class PyCFunctionObjectPtr(PyObjectPtr):
602606

603607
def proxyval(self, visited):
604608
m_ml = self.field('m_ml') # m_ml is a (PyMethodDef*)
605-
ml_name = m_ml['ml_name'].string()
609+
try:
610+
ml_name = m_ml['ml_name'].string()
611+
except UnicodeDecodeError:
612+
ml_name = '<ml_name:UnicodeDecodeError>'
606613

607614
pyop_m_self = self.pyop_field('m_self')
608615
if pyop_m_self.is_null():
@@ -1131,7 +1138,9 @@ def proxyval(self, visited):
11311138
# Convert the int code points to unicode characters, and generate a
11321139
# local unicode instance.
11331140
# This splits surrogate pairs if sizeof(Py_UNICODE) is 2 here (in gdb).
1134-
result = u''.join([_unichr(ucs) for ucs in Py_UNICODEs])
1141+
result = u''.join([
1142+
(_unichr(ucs) if ucs <= 0x10ffff else '\ufffd')
1143+
for ucs in Py_UNICODEs])
11351144
return result
11361145

11371146
def write_repr(self, out, visited):
@@ -1144,6 +1153,41 @@ def write_repr(self, out, visited):
11441153
out.write(val.lstrip('u'))
11451154

11461155

1156+
class wrapperobject(PyObjectPtr):
1157+
_typename = 'wrapperobject'
1158+
1159+
def safe_name(self):
1160+
try:
1161+
name = self.field('descr')['d_base']['name'].string()
1162+
return repr(name)
1163+
except (NullPyObjectPtr, RuntimeError, UnicodeDecodeError):
1164+
return '<unknown name>'
1165+
1166+
def safe_tp_name(self):
1167+
try:
1168+
return self.field('self')['ob_type']['tp_name'].string()
1169+
except (NullPyObjectPtr, RuntimeError, UnicodeDecodeError):
1170+
return '<unknown tp_name>'
1171+
1172+
def safe_self_addresss(self):
1173+
try:
1174+
address = long(self.field('self'))
1175+
return '%#x' % address
1176+
except (NullPyObjectPtr, RuntimeError):
1177+
return '<failed to get self address>'
1178+
1179+
def proxyval(self, visited):
1180+
name = self.safe_name()
1181+
tp_name = self.safe_tp_name()
1182+
self_address = self.safe_self_addresss()
1183+
return ("<method-wrapper %s of %s object at %s>"
1184+
% (name, tp_name, self_address))
1185+
1186+
def write_repr(self, out, visited):
1187+
proxy = self.proxyval(visited)
1188+
out.write(proxy)
1189+
1190+
11471191
def int_from_int(gdbval):
11481192
return int(str(gdbval))
11491193

@@ -1176,11 +1220,13 @@ def to_string (self):
11761220

11771221
def pretty_printer_lookup(gdbval):
11781222
type = gdbval.type.unqualified()
1179-
if type.code == gdb.TYPE_CODE_PTR:
1180-
type = type.target().unqualified()
1181-
t = str(type)
1182-
if t in ("PyObject", "PyFrameObject"):
1183-
return PyObjectPtrPrinter(gdbval)
1223+
if type.code != gdb.TYPE_CODE_PTR:
1224+
return None
1225+
1226+
type = type.target().unqualified()
1227+
t = str(type)
1228+
if t in ("PyObject", "PyFrameObject", "PyUnicodeObject", "wrapperobject"):
1229+
return PyObjectPtrPrinter(gdbval)
11841230

11851231
"""
11861232
During development, I've been manually invoking the code in this way:
@@ -1202,7 +1248,7 @@ def pretty_printer_lookup(gdbval):
12021248
/usr/lib/debug/usr/lib/libpython2.6.so.1.0.debug-gdb.py
12031249
"""
12041250
def register (obj):
1205-
if obj == None:
1251+
if obj is None:
12061252
obj = gdb
12071253

12081254
# Wire up the pretty-printer
@@ -1304,23 +1350,43 @@ def is_other_python_frame(self):
13041350
'''
13051351
if self.is_waiting_for_gil():
13061352
return 'Waiting for the GIL'
1307-
elif self.is_gc_collect():
1353+
1354+
if self.is_gc_collect():
13081355
return 'Garbage-collecting'
1309-
else:
1310-
# Detect invocations of PyCFunction instances:
1311-
older = self.older()
1312-
if older and older._gdbframe.name() == 'PyCFunction_Call':
1313-
# Within that frame:
1314-
# "func" is the local containing the PyObject* of the
1315-
# PyCFunctionObject instance
1316-
# "f" is the same value, but cast to (PyCFunctionObject*)
1317-
# "self" is the (PyObject*) of the 'self'
1318-
try:
1319-
# Use the prettyprinter for the func:
1320-
func = older._gdbframe.read_var('func')
1321-
return str(func)
1322-
except RuntimeError:
1323-
return 'PyCFunction invocation (unable to read "func")'
1356+
1357+
# Detect invocations of PyCFunction instances:
1358+
frame = self._gdbframe
1359+
caller = frame.name()
1360+
if not caller:
1361+
return False
1362+
1363+
if caller == 'PyCFunction_Call':
1364+
arg_name = 'func'
1365+
# Within that frame:
1366+
# "func" is the local containing the PyObject* of the
1367+
# PyCFunctionObject instance
1368+
# "f" is the same value, but cast to (PyCFunctionObject*)
1369+
# "self" is the (PyObject*) of the 'self'
1370+
try:
1371+
# Use the prettyprinter for the func:
1372+
func = frame.read_var(arg_name)
1373+
return str(func)
1374+
except ValueError:
1375+
return ('PyCFunction invocation (unable to read %s: '
1376+
'missing debuginfos?)' % arg_name)
1377+
except RuntimeError:
1378+
return 'PyCFunction invocation (unable to read %s)' % arg_name
1379+
1380+
if caller == 'wrapper_call':
1381+
arg_name = 'wp'
1382+
try:
1383+
func = frame.read_var(arg_name)
1384+
return str(func)
1385+
except ValueError:
1386+
return ('<wrapper_call invocation (unable to read %s: '
1387+
'missing debuginfos?)>' % arg_name)
1388+
except RuntimeError:
1389+
return '<wrapper_call invocation (unable to read %s)>' % arg_name
13241390

13251391
# This frame isn't worth reporting:
13261392
return False
@@ -1368,7 +1434,11 @@ def get_selected_frame(cls):
13681434
def get_selected_python_frame(cls):
13691435
'''Try to obtain the Frame for the python-related code in the selected
13701436
frame, or None'''
1371-
frame = cls.get_selected_frame()
1437+
try:
1438+
frame = cls.get_selected_frame()
1439+
except gdb.error:
1440+
# No frame: Python didn't start yet
1441+
return None
13721442

13731443
while frame:
13741444
if frame.is_python_frame():
@@ -1509,6 +1579,10 @@ def invoke(self, args, from_tty):
15091579
def move_in_stack(move_up):
15101580
'''Move up or down the stack (for the py-up/py-down command)'''
15111581
frame = Frame.get_selected_python_frame()
1582+
if not frame:
1583+
print('Unable to locate python frame')
1584+
return
1585+
15121586
while frame:
15131587
if move_up:
15141588
iter_frame = frame.older()
@@ -1571,6 +1645,10 @@ def __init__(self):
15711645

15721646
def invoke(self, args, from_tty):
15731647
frame = Frame.get_selected_python_frame()
1648+
if not frame:
1649+
print('Unable to locate python frame')
1650+
return
1651+
15741652
while frame:
15751653
if frame.is_python_frame():
15761654
frame.print_summary()
@@ -1588,8 +1666,12 @@ def __init__(self):
15881666

15891667

15901668
def invoke(self, args, from_tty):
1591-
sys.stdout.write('Traceback (most recent call first):\n')
15921669
frame = Frame.get_selected_python_frame()
1670+
if not frame:
1671+
print('Unable to locate python frame')
1672+
return
1673+
1674+
sys.stdout.write('Traceback (most recent call first):\n')
15931675
while frame:
15941676
if frame.is_python_frame():
15951677
frame.print_traceback()

0 commit comments

Comments
 (0)