Skip to content

Commit cc1db4b

Browse files
committed
python-gdb.py: enhance py-bt command
* Add py-bt-full command * py-bt now gives an output similar to a regular Python traceback * py-bt indicates: - if the garbage collector is running - if the thread is waiting for the GIL - detect PyCFunction_Call to get the name of the builtin function
1 parent 3c5ce40 commit cc1db4b

2 files changed

Lines changed: 292 additions & 18 deletions

File tree

Lib/test/test_gdb.py

Lines changed: 123 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,12 @@
1313
from test import test_support
1414
from test.test_support import run_unittest, findfile
1515

16+
# Is this Python configured to support threads?
17+
try:
18+
import thread
19+
except ImportError:
20+
thread = None
21+
1622
def get_gdb_version():
1723
try:
1824
proc = subprocess.Popen(["gdb", "-nx", "--version"],
@@ -728,20 +734,133 @@ def test_up_then_down(self):
728734
class PyBtTests(DebuggerTests):
729735
@unittest.skipIf(python_is_optimized(),
730736
"Python was compiled with optimizations")
731-
def test_basic_command(self):
737+
def test_bt(self):
732738
'Verify that the "py-bt" command works'
733739
bt = self.get_stack_trace(script=self.get_sample_script(),
734740
cmds_after_breakpoint=['py-bt'])
735741
self.assertMultilineMatches(bt,
736742
r'''^.*
737-
#[0-9]+ Frame 0x[0-9a-f]+, for file .*gdb_sample.py, line 7, in bar \(a=1, b=2, c=3\)
743+
Traceback \(most recent call first\):
744+
File ".*gdb_sample.py", line 10, in baz
745+
print\(42\)
746+
File ".*gdb_sample.py", line 7, in bar
747+
baz\(a, b, c\)
748+
File ".*gdb_sample.py", line 4, in foo
749+
bar\(a, b, c\)
750+
File ".*gdb_sample.py", line 12, in <module>
751+
foo\(1, 2, 3\)
752+
''')
753+
754+
@unittest.skipIf(python_is_optimized(),
755+
"Python was compiled with optimizations")
756+
def test_bt_full(self):
757+
'Verify that the "py-bt-full" command works'
758+
bt = self.get_stack_trace(script=self.get_sample_script(),
759+
cmds_after_breakpoint=['py-bt-full'])
760+
self.assertMultilineMatches(bt,
761+
r'''^.*
762+
#[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 7, in bar \(a=1, b=2, c=3\)
738763
baz\(a, b, c\)
739-
#[0-9]+ Frame 0x[0-9a-f]+, for file .*gdb_sample.py, line 4, in foo \(a=1, b=2, c=3\)
764+
#[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 4, in foo \(a=1, b=2, c=3\)
740765
bar\(a, b, c\)
741-
#[0-9]+ Frame 0x[0-9a-f]+, for file .*gdb_sample.py, line 12, in <module> \(\)
766+
#[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 12, in <module> \(\)
742767
foo\(1, 2, 3\)
743768
''')
744769

770+
@unittest.skipUnless(thread,
771+
"Python was compiled without thread support")
772+
def test_threads(self):
773+
'Verify that "py-bt" indicates threads that are waiting for the GIL'
774+
cmd = '''
775+
from threading import Thread
776+
777+
class TestThread(Thread):
778+
# These threads would run forever, but we'll interrupt things with the
779+
# debugger
780+
def run(self):
781+
i = 0
782+
while 1:
783+
i += 1
784+
785+
t = {}
786+
for i in range(4):
787+
t[i] = TestThread()
788+
t[i].start()
789+
790+
# Trigger a breakpoint on the main thread
791+
print 42
792+
793+
'''
794+
# Verify with "py-bt":
795+
gdb_output = self.get_stack_trace(cmd,
796+
cmds_after_breakpoint=['thread apply all py-bt'])
797+
self.assertIn('Waiting for the GIL', gdb_output)
798+
799+
# Verify with "py-bt-full":
800+
gdb_output = self.get_stack_trace(cmd,
801+
cmds_after_breakpoint=['thread apply all py-bt-full'])
802+
self.assertIn('Waiting for the GIL', gdb_output)
803+
804+
@unittest.skipIf(python_is_optimized(),
805+
"Python was compiled with optimizations")
806+
# Some older versions of gdb will fail with
807+
# "Cannot find new threads: generic error"
808+
# unless we add LD_PRELOAD=PATH-TO-libpthread.so.1 as a workaround
809+
@unittest.skipUnless(thread,
810+
"Python was compiled without thread support")
811+
def test_gc(self):
812+
'Verify that "py-bt" indicates if a thread is garbage-collecting'
813+
cmd = ('from gc import collect\n'
814+
'print 42\n'
815+
'def foo():\n'
816+
' collect()\n'
817+
'def bar():\n'
818+
' foo()\n'
819+
'bar()\n')
820+
# Verify with "py-bt":
821+
gdb_output = self.get_stack_trace(cmd,
822+
cmds_after_breakpoint=['break update_refs', 'continue', 'py-bt'],
823+
)
824+
self.assertIn('Garbage-collecting', gdb_output)
825+
826+
# Verify with "py-bt-full":
827+
gdb_output = self.get_stack_trace(cmd,
828+
cmds_after_breakpoint=['break update_refs', 'continue', 'py-bt-full'],
829+
)
830+
self.assertIn('Garbage-collecting', gdb_output)
831+
832+
@unittest.skipIf(python_is_optimized(),
833+
"Python was compiled with optimizations")
834+
# Some older versions of gdb will fail with
835+
# "Cannot find new threads: generic error"
836+
# unless we add LD_PRELOAD=PATH-TO-libpthread.so.1 as a workaround
837+
@unittest.skipUnless(thread,
838+
"Python was compiled without thread support")
839+
def test_pycfunction(self):
840+
'Verify that "py-bt" displays invocations of PyCFunction instances'
841+
# Tested function must not be defined with METH_NOARGS or METH_O,
842+
# otherwise call_function() doesn't call PyCFunction_Call()
843+
cmd = ('from time import gmtime\n'
844+
'def foo():\n'
845+
' gmtime(1)\n'
846+
'def bar():\n'
847+
' foo()\n'
848+
'bar()\n')
849+
# Verify with "py-bt":
850+
gdb_output = self.get_stack_trace(cmd,
851+
breakpoint='time_gmtime',
852+
cmds_after_breakpoint=['bt', 'py-bt'],
853+
)
854+
self.assertIn('<built-in function gmtime', gdb_output)
855+
856+
# Verify with "py-bt-full":
857+
gdb_output = self.get_stack_trace(cmd,
858+
breakpoint='time_gmtime',
859+
cmds_after_breakpoint=['py-bt-full'],
860+
)
861+
self.assertIn('#0 <built-in function gmtime', gdb_output)
862+
863+
745864
class PyPrintTests(DebuggerTests):
746865
@unittest.skipIf(python_is_optimized(),
747866
"Python was compiled with optimizations")

0 commit comments

Comments
 (0)