Skip to content

Commit 760002c

Browse files
committed
gh-150206: Fix gdb test failure with tail-call interpreter
1 parent 87411d0 commit 760002c

2 files changed

Lines changed: 47 additions & 0 deletions

File tree

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix the ``py-bt``, ``py-bt-full``, ``py-list``, ``py-locals`` and
2+
``py-print`` gdb commands losing the innermost Python frames when debugging an
3+
interpreter built with the tail-call interpreter (``--with-tail-call-interp``).

Tools/gdb/libpython.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1717,6 +1717,7 @@ class Frame(object):
17171717
'''
17181718
def __init__(self, gdbframe):
17191719
self._gdbframe = gdbframe
1720+
self._is_tail_call_interp = None # lazy initialization
17201721

17211722
def older(self):
17221723
older = self._gdbframe.older()
@@ -1855,8 +1856,51 @@ def is_gc_collect(self):
18551856
'gc_collect_young', 'gc_collect_increment',
18561857
)
18571858

1859+
1860+
def is_tail_call_interp(self):
1861+
if self._is_tail_call_interp is None:
1862+
self._is_tail_call_interp = False
1863+
try:
1864+
if gdb.lookup_static_symbol('instruction_funcptr_handler_table'):
1865+
self._is_tail_call_interp = True
1866+
except (AttributeError, gdb.error):
1867+
# Older gdb without lookup_static_symbol, or no debug info.
1868+
pass
1869+
return self._is_tail_call_interp
1870+
1871+
def get_frame_for_tail_call(self):
1872+
'''
1873+
For tail call interpreter, `_gdbframe.read_var('frame')` isn't reliable,
1874+
so we have to read from `thread_state['current_frame']`.
1875+
1876+
A more complex case is, _PyEval_EvalFrameDefault C frame can be nested.
1877+
(e.g. see `SAMPLE_WITH_C_CALL` in `test_misc.py`) So we have to walk the interpreter frames,
1878+
skip several inner shims and return the first frame of the matching loop.
1879+
'''
1880+
depth = 0 # depth means how many inner evalframes are there.
1881+
newer = self.newer()
1882+
while newer:
1883+
if newer.is_evalframe():
1884+
depth += 1
1885+
newer = newer.newer()
1886+
1887+
interp_frame = PyFramePtr.get_thread_local_frame()
1888+
if depth == 0: # normal case: just return
1889+
return interp_frame
1890+
1891+
while depth > 0: # this loop walks through the interpreter frames, skipping shims.
1892+
interp_frame = interp_frame.previous()
1893+
if interp_frame.is_shim():
1894+
depth -= 1
1895+
1896+
return interp_frame.previous() # we can't return shim, but its previous frame
1897+
18581898
def get_pyop(self):
18591899
try:
1900+
if self.is_tail_call_interp():
1901+
frame = self.get_frame_for_tail_call()
1902+
if frame is not None and not frame.is_optimized_out():
1903+
return frame
18601904
frame = self._gdbframe.read_var('frame')
18611905
frame = PyFramePtr(frame)
18621906
if not frame.is_optimized_out():

0 commit comments

Comments
 (0)