@@ -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