2023-09-28 08:24:15 -03:00
|
|
|
import textwrap
|
|
|
|
import unittest
|
|
|
|
from test import support
|
|
|
|
from test.support import python_is_optimized
|
|
|
|
|
2023-09-28 14:04:01 -03:00
|
|
|
from .util import setup_module, DebuggerTests, CET_PROTECTION, SAMPLE_SCRIPT
|
2023-09-28 08:24:15 -03:00
|
|
|
|
|
|
|
|
|
|
|
def setUpModule():
|
|
|
|
setup_module()
|
|
|
|
|
|
|
|
|
|
|
|
class PyBtTests(DebuggerTests):
|
|
|
|
@unittest.skipIf(python_is_optimized(),
|
|
|
|
"Python was compiled with optimizations")
|
|
|
|
def test_bt(self):
|
|
|
|
'Verify that the "py-bt" command works'
|
2023-09-28 14:04:01 -03:00
|
|
|
bt = self.get_stack_trace(script=SAMPLE_SCRIPT,
|
2023-09-28 08:24:15 -03:00
|
|
|
cmds_after_breakpoint=['py-bt'])
|
|
|
|
self.assertMultilineMatches(bt,
|
|
|
|
r'''^.*
|
|
|
|
Traceback \(most recent call first\):
|
|
|
|
<built-in method id of module object .*>
|
|
|
|
File ".*gdb_sample.py", line 10, in baz
|
|
|
|
id\(42\)
|
|
|
|
File ".*gdb_sample.py", line 7, in bar
|
|
|
|
baz\(a, b, c\)
|
|
|
|
File ".*gdb_sample.py", line 4, in foo
|
|
|
|
bar\(a=a, b=b, c=c\)
|
|
|
|
File ".*gdb_sample.py", line 12, in <module>
|
|
|
|
foo\(1, 2, 3\)
|
|
|
|
''')
|
|
|
|
|
|
|
|
@unittest.skipIf(python_is_optimized(),
|
|
|
|
"Python was compiled with optimizations")
|
|
|
|
def test_bt_full(self):
|
|
|
|
'Verify that the "py-bt-full" command works'
|
2023-09-28 14:04:01 -03:00
|
|
|
bt = self.get_stack_trace(script=SAMPLE_SCRIPT,
|
2023-09-28 08:24:15 -03:00
|
|
|
cmds_after_breakpoint=['py-bt-full'])
|
|
|
|
self.assertMultilineMatches(bt,
|
|
|
|
r'''^.*
|
|
|
|
#[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 7, in bar \(a=1, b=2, c=3\)
|
|
|
|
baz\(a, b, c\)
|
|
|
|
#[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 4, in foo \(a=1, b=2, c=3\)
|
|
|
|
bar\(a=a, b=b, c=c\)
|
|
|
|
#[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 12, in <module> \(\)
|
|
|
|
foo\(1, 2, 3\)
|
|
|
|
''')
|
|
|
|
|
|
|
|
@unittest.skipIf(python_is_optimized(),
|
|
|
|
"Python was compiled with optimizations")
|
2024-04-04 17:27:14 -03:00
|
|
|
@support.requires_gil_enabled
|
2023-09-28 08:24:15 -03:00
|
|
|
@support.requires_resource('cpu')
|
|
|
|
def test_threads(self):
|
|
|
|
'Verify that "py-bt" indicates threads that are waiting for the GIL'
|
|
|
|
cmd = '''
|
|
|
|
from threading import Thread
|
|
|
|
|
|
|
|
class TestThread(Thread):
|
|
|
|
# These threads would run forever, but we'll interrupt things with the
|
|
|
|
# debugger
|
|
|
|
def run(self):
|
|
|
|
i = 0
|
|
|
|
while 1:
|
|
|
|
i += 1
|
|
|
|
|
|
|
|
t = {}
|
|
|
|
for i in range(4):
|
|
|
|
t[i] = TestThread()
|
|
|
|
t[i].start()
|
|
|
|
|
|
|
|
# Trigger a breakpoint on the main thread
|
|
|
|
id(42)
|
|
|
|
|
|
|
|
'''
|
|
|
|
# Verify with "py-bt":
|
|
|
|
gdb_output = self.get_stack_trace(cmd,
|
|
|
|
cmds_after_breakpoint=['thread apply all py-bt'])
|
|
|
|
self.assertIn('Waiting for the GIL', gdb_output)
|
|
|
|
|
|
|
|
# Verify with "py-bt-full":
|
|
|
|
gdb_output = self.get_stack_trace(cmd,
|
|
|
|
cmds_after_breakpoint=['thread apply all py-bt-full'])
|
|
|
|
self.assertIn('Waiting for the GIL', gdb_output)
|
|
|
|
|
|
|
|
@unittest.skipIf(python_is_optimized(),
|
|
|
|
"Python was compiled with optimizations")
|
|
|
|
# Some older versions of gdb will fail with
|
|
|
|
# "Cannot find new threads: generic error"
|
|
|
|
# unless we add LD_PRELOAD=PATH-TO-libpthread.so.1 as a workaround
|
|
|
|
def test_gc(self):
|
|
|
|
'Verify that "py-bt" indicates if a thread is garbage-collecting'
|
|
|
|
cmd = ('from gc import collect\n'
|
|
|
|
'id(42)\n'
|
|
|
|
'def foo():\n'
|
|
|
|
' collect()\n'
|
|
|
|
'def bar():\n'
|
|
|
|
' foo()\n'
|
|
|
|
'bar()\n')
|
|
|
|
# Verify with "py-bt":
|
|
|
|
gdb_output = self.get_stack_trace(cmd,
|
|
|
|
cmds_after_breakpoint=['break update_refs', 'continue', 'py-bt'],
|
|
|
|
)
|
|
|
|
self.assertIn('Garbage-collecting', gdb_output)
|
|
|
|
|
|
|
|
# Verify with "py-bt-full":
|
|
|
|
gdb_output = self.get_stack_trace(cmd,
|
|
|
|
cmds_after_breakpoint=['break update_refs', 'continue', 'py-bt-full'],
|
|
|
|
)
|
|
|
|
self.assertIn('Garbage-collecting', gdb_output)
|
|
|
|
|
|
|
|
@unittest.skipIf(python_is_optimized(),
|
|
|
|
"Python was compiled with optimizations")
|
|
|
|
def test_wrapper_call(self):
|
|
|
|
cmd = textwrap.dedent('''
|
|
|
|
class MyList(list):
|
|
|
|
def __init__(self):
|
|
|
|
super(*[]).__init__() # wrapper_call()
|
|
|
|
|
|
|
|
id("first break point")
|
|
|
|
l = MyList()
|
|
|
|
''')
|
|
|
|
cmds_after_breakpoint = ['break wrapper_call', 'continue']
|
|
|
|
if CET_PROTECTION:
|
|
|
|
# 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.
|
|
|
|
cmds_after_breakpoint.append('next')
|
|
|
|
cmds_after_breakpoint.append('py-bt')
|
|
|
|
|
|
|
|
# Verify with "py-bt":
|
|
|
|
gdb_output = self.get_stack_trace(cmd,
|
|
|
|
cmds_after_breakpoint=cmds_after_breakpoint)
|
|
|
|
self.assertRegex(gdb_output,
|
|
|
|
r"<method-wrapper u?'__init__' of MyList object at ")
|