Skip to content

Commit a4a33ff

Browse files
colesburyvstinner
andauthored
[3.13] gh-142095: Use thread local frame info in py-bt and py-bt-full when available (gh-143371) (#143567)
In optimized and `-Og` builds, arguments and local variables are frequently unavailable in gdb. This makes `py-bt` fail to print anything useful. Use the `PyThreadState*` pointers `_Py_tss_gilstate` and `Py_tss_tstate` to find the interpreter frame if we can't get the frame from the `_PyEval_EvalFrameDefault` call. (cherry picked from commit 49c3b0a) Co-authored-by: Victor Stinner <vstinner@python.org>
1 parent cb42565 commit a4a33ff

File tree

2 files changed

+104
-75
lines changed

2 files changed

+104
-75
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Make gdb 'py-bt' command use frame from thread local state when available.
2+
Patch by Sam Gross and Victor Stinner.

Tools/gdb/libpython.py

Lines changed: 102 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ def write(self, data):
144144
def getvalue(self):
145145
return self._val
146146

147+
147148
class PyObjectPtr(object):
148149
"""
149150
Class wrapping a gdb.Value that's either a (PyObject*) within the
@@ -1012,30 +1013,49 @@ def write_repr(self, out, visited):
10121013
return
10131014
return self._frame.write_repr(out, visited)
10141015

1015-
def print_traceback(self):
1016-
if self.is_optimized_out():
1017-
sys.stdout.write(' %s\n' % FRAME_INFO_OPTIMIZED_OUT)
1018-
return
1019-
return self._frame.print_traceback()
1020-
10211016
class PyFramePtr:
10221017

10231018
def __init__(self, gdbval):
10241019
self._gdbval = gdbval
1020+
if self.is_optimized_out():
1021+
return
1022+
self.co = self._f_code()
1023+
if self.is_shim():
1024+
return
1025+
self.co_name = self.co.pyop_field('co_name')
1026+
self.co_filename = self.co.pyop_field('co_filename')
10251027

1026-
if not self.is_optimized_out():
1028+
self.f_lasti = self._f_lasti()
1029+
self.co_nlocals = int_from_int(self.co.field('co_nlocals'))
1030+
pnames = self.co.field('co_localsplusnames')
1031+
self.co_localsplusnames = PyTupleObjectPtr.from_pyobject_ptr(pnames)
1032+
1033+
@staticmethod
1034+
def get_thread_state():
1035+
exprs = [
1036+
'_Py_tss_gilstate', # 3.15+
1037+
'_Py_tss_tstate', # 3.12+ (and not when GIL is released)
1038+
'pthread_getspecific(_PyRuntime.autoTSSkey._key)', # only live programs
1039+
'((struct pthread*)$fs_base)->specific_1stblock[_PyRuntime.autoTSSkey._key].data' # x86-64
1040+
]
1041+
for expr in exprs:
10271042
try:
1028-
self.co = self._f_code()
1029-
self.co_name = self.co.pyop_field('co_name')
1030-
self.co_filename = self.co.pyop_field('co_filename')
1031-
1032-
self.f_lasti = self._f_lasti()
1033-
self.co_nlocals = int_from_int(self.co.field('co_nlocals'))
1034-
pnames = self.co.field('co_localsplusnames')
1035-
self.co_localsplusnames = PyTupleObjectPtr.from_pyobject_ptr(pnames)
1036-
self._is_code = True
1037-
except:
1038-
self._is_code = False
1043+
val = gdb.parse_and_eval(f'(PyThreadState*)({expr})')
1044+
except gdb.error:
1045+
continue
1046+
if int(val) != 0:
1047+
return val
1048+
return None
1049+
1050+
@staticmethod
1051+
def get_thread_local_frame():
1052+
thread_state = PyFramePtr.get_thread_state()
1053+
if thread_state is None:
1054+
return None
1055+
current_frame = thread_state['current_frame']
1056+
if int(current_frame) == 0:
1057+
return None
1058+
return PyFramePtr(current_frame)
10391059

10401060
def is_optimized_out(self):
10411061
return self._gdbval.is_optimized_out
@@ -1088,6 +1108,8 @@ def is_shim(self):
10881108
return self._f_special("owner", int) == FRAME_OWNED_BY_CSTACK
10891109

10901110
def previous(self):
1111+
if int(self._gdbval['previous']) == 0:
1112+
return None
10911113
return self._f_special("previous", PyFramePtr)
10921114

10931115
def iter_globals(self):
@@ -1216,6 +1238,27 @@ def print_traceback(self):
12161238
lineno,
12171239
self.co_name.proxyval(visited)))
12181240

1241+
def print_traceback_until_shim(self, frame_index=None):
1242+
# Print traceback for _PyInterpreterFrame and return previous frame
1243+
interp_frame = self
1244+
while True:
1245+
if not interp_frame:
1246+
sys.stdout.write(' (unable to read python frame information)\n')
1247+
return None
1248+
if interp_frame.is_shim():
1249+
return interp_frame.previous()
1250+
1251+
if frame_index is not None:
1252+
line = interp_frame.get_truncated_repr(MAX_OUTPUT_LEN)
1253+
sys.stdout.write('#%i %s\n' % (frame_index, line))
1254+
else:
1255+
interp_frame.print_traceback()
1256+
if not interp_frame.is_optimized_out():
1257+
line = interp_frame.current_line()
1258+
if line is not None:
1259+
sys.stdout.write(' %s\n' % line.strip())
1260+
interp_frame = interp_frame.previous()
1261+
12191262
def get_truncated_repr(self, maxlen):
12201263
'''
12211264
Get a repr-like string for the data, but truncate it at "maxlen" bytes
@@ -1825,50 +1868,17 @@ def get_selected_bytecode_frame(cls):
18251868
def print_summary(self):
18261869
if self.is_evalframe():
18271870
interp_frame = self.get_pyop()
1828-
while True:
1829-
if interp_frame:
1830-
if interp_frame.is_shim():
1831-
break
1832-
line = interp_frame.get_truncated_repr(MAX_OUTPUT_LEN)
1833-
sys.stdout.write('#%i %s\n' % (self.get_index(), line))
1834-
if not interp_frame.is_optimized_out():
1835-
line = interp_frame.current_line()
1836-
if line is not None:
1837-
sys.stdout.write(' %s\n' % line.strip())
1838-
else:
1839-
sys.stdout.write('#%i (unable to read python frame information)\n' % self.get_index())
1840-
break
1841-
interp_frame = interp_frame.previous()
1871+
if interp_frame:
1872+
interp_frame.print_traceback_until_shim(self.get_index())
1873+
else:
1874+
sys.stdout.write('#%i (unable to read python frame information)\n' % self.get_index())
18421875
else:
18431876
info = self.is_other_python_frame()
18441877
if info:
18451878
sys.stdout.write('#%i %s\n' % (self.get_index(), info))
18461879
else:
18471880
sys.stdout.write('#%i\n' % self.get_index())
18481881

1849-
def print_traceback(self):
1850-
if self.is_evalframe():
1851-
interp_frame = self.get_pyop()
1852-
while True:
1853-
if interp_frame:
1854-
if interp_frame.is_shim():
1855-
break
1856-
interp_frame.print_traceback()
1857-
if not interp_frame.is_optimized_out():
1858-
line = interp_frame.current_line()
1859-
if line is not None:
1860-
sys.stdout.write(' %s\n' % line.strip())
1861-
else:
1862-
sys.stdout.write(' (unable to read python frame information)\n')
1863-
break
1864-
interp_frame = interp_frame.previous()
1865-
else:
1866-
info = self.is_other_python_frame()
1867-
if info:
1868-
sys.stdout.write(' %s\n' % info)
1869-
else:
1870-
sys.stdout.write(' (not a python frame)\n')
1871-
18721882
class PyList(gdb.Command):
18731883
'''List the current Python source code, if any
18741884
@@ -2012,6 +2022,41 @@ def invoke(self, args, from_tty):
20122022
PyUp()
20132023
PyDown()
20142024

2025+
2026+
def print_traceback_helper(full_info):
2027+
frame = Frame.get_selected_python_frame()
2028+
interp_frame = PyFramePtr.get_thread_local_frame()
2029+
if not frame and not interp_frame:
2030+
print('Unable to locate python frame')
2031+
return
2032+
2033+
sys.stdout.write('Traceback (most recent call first):\n')
2034+
if frame:
2035+
while frame:
2036+
frame_index = frame.get_index() if full_info else None
2037+
if frame.is_evalframe():
2038+
pyop = frame.get_pyop()
2039+
if pyop is not None:
2040+
# Use the _PyInterpreterFrame from the gdb frame
2041+
interp_frame = pyop
2042+
if interp_frame:
2043+
interp_frame = interp_frame.print_traceback_until_shim(frame_index)
2044+
else:
2045+
sys.stdout.write(' (unable to read python frame information)\n')
2046+
else:
2047+
info = frame.is_other_python_frame()
2048+
if full_info:
2049+
if info:
2050+
sys.stdout.write('#%i %s\n' % (frame_index, info))
2051+
elif info:
2052+
sys.stdout.write(' %s\n' % info)
2053+
frame = frame.older()
2054+
else:
2055+
# Fall back to just using the thread-local frame
2056+
while interp_frame:
2057+
interp_frame = interp_frame.print_traceback_until_shim()
2058+
2059+
20152060
class PyBacktraceFull(gdb.Command):
20162061
'Display the current python frame and all the frames within its call stack (if any)'
20172062
def __init__(self):
@@ -2022,15 +2067,7 @@ def __init__(self):
20222067

20232068

20242069
def invoke(self, args, from_tty):
2025-
frame = Frame.get_selected_python_frame()
2026-
if not frame:
2027-
print('Unable to locate python frame')
2028-
return
2029-
2030-
while frame:
2031-
if frame.is_python_frame():
2032-
frame.print_summary()
2033-
frame = frame.older()
2070+
print_traceback_helper(full_info=True)
20342071

20352072
PyBacktraceFull()
20362073

@@ -2042,18 +2079,8 @@ def __init__(self):
20422079
gdb.COMMAND_STACK,
20432080
gdb.COMPLETE_NONE)
20442081

2045-
20462082
def invoke(self, args, from_tty):
2047-
frame = Frame.get_selected_python_frame()
2048-
if not frame:
2049-
print('Unable to locate python frame')
2050-
return
2051-
2052-
sys.stdout.write('Traceback (most recent call first):\n')
2053-
while frame:
2054-
if frame.is_python_frame():
2055-
frame.print_traceback()
2056-
frame = frame.older()
2083+
print_traceback_helper(full_info=False)
20572084

20582085
PyBacktrace()
20592086

0 commit comments

Comments
 (0)