bpo-32962: Backport python-gdb.py and test_gdb.py from master (GH-7710) (GH-7711)

* 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 019d33b7a4)

* bpo-32962: python-gdb catchs UnicodeDecodeError (GH-7693)

python-gdb now catchs UnicodeDecodeError exceptions when calling
string().

(cherry picked from commit d22fc0bc7d)

* bpo-32962: Fix test_gdb failure in debug build with -mcet -fcf-protection -O0 (GH-6754)

When Python is built with the intel control-flow protection flags,
-mcet -fcf-protection, gdb is not able to read the stack without
actually jumping inside the function. This means an extra
'next' command is required to make the $pc (program counter)
enter the function and make the stack of the function exposed to gdb.

(cherry picked from commit 9b7c74ca32)

(cherry picked from commit ca4cb8492c)
This commit is contained in:
Victor Stinner 2018-06-15 19:44:00 +02:00 committed by GitHub
parent d85d479f0e
commit 5279759f52
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 48 additions and 18 deletions

View File

@ -168,7 +168,11 @@ class DebuggerTests(unittest.TestCase):
commands += ['set print entry-values no'] commands += ['set print entry-values no']
if cmds_after_breakpoint: if cmds_after_breakpoint:
commands += cmds_after_breakpoint # bpo-32962: When Python is compiled with -mcet -fcf-protection,
# arguments are unusable before running the first instruction
# of the function entry point. The 'next' command makes the
# required first step.
commands += ['next'] + cmds_after_breakpoint
else: else:
commands += ['backtrace'] commands += ['backtrace']
@ -859,9 +863,12 @@ id(42)
id("first break point") id("first break point")
l = MyList() l = MyList()
''') ''')
# bpo-32962: same case as in get_stack_trace():
# we need an additional 'next' command in order to read
# arguments of the innermost function of the call stack.
# Verify with "py-bt": # Verify with "py-bt":
gdb_output = self.get_stack_trace(cmd, gdb_output = self.get_stack_trace(cmd,
cmds_after_breakpoint=['break wrapper_call', 'continue', 'py-bt']) cmds_after_breakpoint=['break wrapper_call', 'continue', 'next', 'py-bt'])
self.assertRegex(gdb_output, self.assertRegex(gdb_output,
r"<method-wrapper u?'__init__' of MyList object at ") r"<method-wrapper u?'__init__' of MyList object at ")

View File

@ -0,0 +1 @@
Fixed test_gdb when Python is compiled with flags -mcet -fcf-protection -O0.

View File

@ -0,0 +1,2 @@
python-gdb now catchs ValueError on read_var(): when Python has no debug
symbols for example.

View File

@ -0,0 +1,2 @@
python-gdb now catchs ``UnicodeDecodeError`` exceptions when calling
``string()``.

View File

@ -270,12 +270,13 @@ class PyObjectPtr(object):
def safe_tp_name(self): def safe_tp_name(self):
try: try:
return self.type().field('tp_name').string() ob_type = self.type()
except NullPyObjectPtr: tp_name = ob_type.field('tp_name')
# NULL tp_name? return tp_name.string()
return 'unknown' # NullPyObjectPtr: NULL tp_name?
except RuntimeError: # RuntimeError: Can't even read the object at all?
# Can't even read the object at all? # UnicodeDecodeError: Failed to decode tp_name bytestring
except (NullPyObjectPtr, RuntimeError, UnicodeDecodeError):
return 'unknown' return 'unknown'
def proxyval(self, visited): def proxyval(self, visited):
@ -349,7 +350,9 @@ class PyObjectPtr(object):
try: try:
tp_name = t.field('tp_name').string() tp_name = t.field('tp_name').string()
tp_flags = int(t.field('tp_flags')) tp_flags = int(t.field('tp_flags'))
except RuntimeError: # RuntimeError: NULL pointers
# UnicodeDecodeError: string() fails to decode the bytestring
except (RuntimeError, UnicodeDecodeError):
# Handle any kind of error e.g. NULL ptrs by simply using the base # Handle any kind of error e.g. NULL ptrs by simply using the base
# class # class
return cls return cls
@ -617,7 +620,10 @@ class PyCFunctionObjectPtr(PyObjectPtr):
def proxyval(self, visited): def proxyval(self, visited):
m_ml = self.field('m_ml') # m_ml is a (PyMethodDef*) m_ml = self.field('m_ml') # m_ml is a (PyMethodDef*)
ml_name = m_ml['ml_name'].string() try:
ml_name = m_ml['ml_name'].string()
except UnicodeDecodeError:
ml_name = '<ml_name:UnicodeDecodeError>'
pyop_m_self = self.pyop_field('m_self') pyop_m_self = self.pyop_field('m_self')
if pyop_m_self.is_null(): if pyop_m_self.is_null():
@ -1340,13 +1346,13 @@ class wrapperobject(PyObjectPtr):
try: try:
name = self.field('descr')['d_base']['name'].string() name = self.field('descr')['d_base']['name'].string()
return repr(name) return repr(name)
except (NullPyObjectPtr, RuntimeError): except (NullPyObjectPtr, RuntimeError, UnicodeDecodeError):
return '<unknown name>' return '<unknown name>'
def safe_tp_name(self): def safe_tp_name(self):
try: try:
return self.field('self')['ob_type']['tp_name'].string() return self.field('self')['ob_type']['tp_name'].string()
except (NullPyObjectPtr, RuntimeError): except (NullPyObjectPtr, RuntimeError, UnicodeDecodeError):
return '<unknown tp_name>' return '<unknown tp_name>'
def safe_self_addresss(self): def safe_self_addresss(self):
@ -1541,6 +1547,7 @@ class Frame(object):
return False return False
if caller == 'PyCFunction_Call': if caller == 'PyCFunction_Call':
arg_name = 'func'
# Within that frame: # Within that frame:
# "func" is the local containing the PyObject* of the # "func" is the local containing the PyObject* of the
# PyCFunctionObject instance # PyCFunctionObject instance
@ -1548,24 +1555,35 @@ class Frame(object):
# "self" is the (PyObject*) of the 'self' # "self" is the (PyObject*) of the 'self'
try: try:
# Use the prettyprinter for the func: # Use the prettyprinter for the func:
func = frame.read_var('func') func = frame.read_var(arg_name)
return str(func) return str(func)
except ValueError:
return ('PyCFunction invocation (unable to read %s: '
'missing debuginfos?)' % arg_name)
except RuntimeError: except RuntimeError:
return 'PyCFunction invocation (unable to read "func")' return 'PyCFunction invocation (unable to read %s)' % arg_name
elif caller == '_PyCFunction_FastCallDict': elif caller == '_PyCFunction_FastCallDict':
arg_name = 'func_obj'
try: try:
func = frame.read_var('func_obj') func = frame.read_var(arg_name)
return str(func) return str(func)
except ValueError:
return ('PyCFunction invocation (unable to read %s: '
'missing debuginfos?)' % arg_name)
except RuntimeError: except RuntimeError:
return 'PyCFunction invocation (unable to read "func_obj")' return 'PyCFunction invocation (unable to read %s)' % arg_name
if caller == 'wrapper_call': if caller == 'wrapper_call':
arg_name = 'wp'
try: try:
func = frame.read_var('wp') func = frame.read_var(arg_name)
return str(func) return str(func)
except ValueError:
return ('<wrapper_call invocation (unable to read %s: '
'missing debuginfos?)>' % arg_name)
except RuntimeError: except RuntimeError:
return '<wrapper_call invocation>' return '<wrapper_call invocation (unable to read %s)>' % arg_name
# This frame isn't worth reporting: # This frame isn't worth reporting:
return False return False