test_gdb.py 35.9 KB
Newer Older
1 2 3 4 5 6 7
# Verify that gdb can pretty-print the various PyObject* types
#
# The code for testing gdb was adapted from similar work in Unladen Swallow's
# Lib/test/test_jit_gdb.py

import os
import re
8
import pprint
9 10
import subprocess
import sys
11
import sysconfig
12
import unittest
13
import locale
14

15 16 17 18 19 20
# Is this Python configured to support threads?
try:
    import _thread
except ImportError:
    _thread = None

21
from test import support
22
from test.support import run_unittest, findfile, python_is_optimized
23 24

try:
25
    gdb_version, _ = subprocess.Popen(["gdb", "-nx", "--version"],
26 27 28 29 30
                                      stdout=subprocess.PIPE).communicate()
except OSError:
    # This is what "no gdb" looks like.  There may, however, be other
    # errors that manifest this way too.
    raise unittest.SkipTest("Couldn't find gdb on the path")
31 32 33 34
gdb_version_number = re.search(b"^GNU gdb [^\d]*(\d+)\.(\d)", gdb_version)
gdb_major_version = int(gdb_version_number.group(1))
gdb_minor_version = int(gdb_version_number.group(2))
if gdb_major_version < 7:
35
    raise unittest.SkipTest("gdb versions before 7.0 didn't support python embedding"
36
                            " Saw:\n" + gdb_version.decode('ascii', 'replace'))
37

38 39 40
if not sysconfig.is_python_build():
    raise unittest.SkipTest("test_gdb only works on source builds at the moment.")

41 42 43 44
# Location of custom hooks file in a repository checkout.
checkout_hook_path = os.path.join(os.path.dirname(sys.executable),
                                  'python-gdb.py')

45 46
PYTHONHASHSEED = '123'

47 48 49 50 51 52 53 54 55 56
def run_gdb(*args, **env_vars):
    """Runs gdb in --batch mode with the additional arguments given by *args.

    Returns its (stdout, stderr) decoded from utf-8 using the replace handler.
    """
    if env_vars:
        env = os.environ.copy()
        env.update(env_vars)
    else:
        env = None
57 58 59
    # -nx: Do not execute commands from any .gdbinit initialization files
    #      (issue #22188)
    base_cmd = ('gdb', '--batch', '-nx')
60 61 62 63 64 65 66
    if (gdb_major_version, gdb_minor_version) >= (7, 4):
        base_cmd += ('-iex', 'add-auto-load-safe-path ' + checkout_hook_path)
    out, err = subprocess.Popen(base_cmd + args,
        stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env,
        ).communicate()
    return out.decode('utf-8', 'replace'), err.decode('utf-8', 'replace')

67
# Verify that "gdb" was built with the embedded python support enabled:
68
gdbpy_version, _ = run_gdb("--eval-command=python import sys; print(sys.version_info)")
69
if not gdbpy_version:
70 71
    raise unittest.SkipTest("gdb not built with embedded python support")

Nick Coghlan's avatar
Nick Coghlan committed
72 73
# Verify that "gdb" can load our custom hooks, as OS security settings may
# disallow this without a customised .gdbinit.
74 75 76 77 78
cmd = ['--args', sys.executable]
_, gdbpy_errors = run_gdb('--args', sys.executable)
if "auto-loading has been declined" in gdbpy_errors:
    msg = "gdb security settings prevent use of custom hooks: "
    raise unittest.SkipTest(msg + gdbpy_errors.rstrip())
79

80 81
def gdb_has_frame_select():
    # Does this build of gdb have gdb.Frame.select ?
82 83
    stdout, _ = run_gdb("--eval-command=python print(dir(gdb.Frame))")
    m = re.match(r'.*\[(.*)\].*', stdout)
84 85
    if not m:
        raise unittest.SkipTest("Unable to parse output from gdb.Frame.select test")
86 87
    gdb_frame_dir = m.group(1).split(', ')
    return "'select'" in gdb_frame_dir
88 89

HAS_PYUP_PYDOWN = gdb_has_frame_select()
90

91 92
BREAKPOINT_FN='builtin_id'

93 94 95 96 97
class DebuggerTests(unittest.TestCase):

    """Test that the debugger can debug Python."""

    def get_stack_trace(self, source=None, script=None,
98
                        breakpoint=BREAKPOINT_FN,
99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115
                        cmds_after_breakpoint=None,
                        import_site=False):
        '''
        Run 'python -c SOURCE' under gdb with a breakpoint.

        Support injecting commands after the breakpoint is reached

        Returns the stdout from gdb

        cmds_after_breakpoint: if provided, a list of strings: gdb commands
        '''
        # We use "set breakpoint pending yes" to avoid blocking with a:
        #   Function "foo" not defined.
        #   Make breakpoint pending on future shared library load? (y or [n])
        # error, which typically happens python is dynamically linked (the
        # breakpoints of interest are to be found in the shared library)
        # When this happens, we still get:
116
        #   Function "textiowrapper_write" not defined.
117 118 119 120 121 122 123 124 125
        # emitted to stderr each time, alas.

        # Initially I had "--eval-command=continue" here, but removed it to
        # avoid repeated print breakpoints when traversing hierarchical data
        # structures

        # Generate a list of commands in gdb's language:
        commands = ['set breakpoint pending yes',
                    'break %s' % breakpoint,
126 127 128 129 130 131 132 133 134 135 136

                    # The tests assume that the first frame of printed
                    #  backtrace will not contain program counter,
                    #  that is however not guaranteed by gdb
                    #  therefore we need to use 'set print address off' to
                    #  make sure the counter is not there. For example:
                    # #0 in PyObject_Print ...
                    #  is assumed, but sometimes this can be e.g.
                    # #0 0x00003fffb7dd1798 in PyObject_Print ...
                    'set print address off',

137
                    'run']
138 139 140 141 142 143 144 145 146 147

        # GDB as of 7.4 onwards can distinguish between the
        # value of a variable at entry vs current value:
        #   http://sourceware.org/gdb/onlinedocs/gdb/Variables.html
        # which leads to the selftests failing with errors like this:
        #   AssertionError: 'v@entry=()' != '()'
        # Disable this:
        if (gdb_major_version, gdb_minor_version) >= (7, 4):
            commands += ['set print entry-values no']

148 149 150 151 152 153 154 155
        if cmds_after_breakpoint:
            commands += cmds_after_breakpoint
        else:
            commands += ['backtrace']

        # print commands

        # Use "commands" to generate the arguments with which to invoke "gdb":
156
        args = ["gdb", "--batch", "-nx"]
157 158 159 160 161 162 163 164 165 166 167 168 169 170
        args += ['--eval-command=%s' % cmd for cmd in commands]
        args += ["--args",
                 sys.executable]

        if not import_site:
            # -S suppresses the default 'import site'
            args += ["-S"]

        if source:
            args += ["-c", source]
        elif script:
            args += [script]

        # print args
171
        # print (' '.join(args))
172 173

        # Use "args" to invoke gdb, capturing stdout, stderr:
174
        out, err = run_gdb(*args, PYTHONHASHSEED=PYTHONHASHSEED)
175

176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194
        errlines = err.splitlines()
        unexpected_errlines = []

        # Ignore some benign messages on stderr.
        ignore_patterns = (
            'Function "%s" not defined.' % breakpoint,
            "warning: no loadable sections found in added symbol-file"
            " system-supplied DSO",
            "warning: Unable to find libthread_db matching"
            " inferior's thread library, thread debugging will"
            " not be available.",
            "warning: Cannot initialize thread debugging"
            " library: Debugger service failed",
            'warning: Could not load shared library symbols for '
            'linux-vdso.so',
            'warning: Could not load shared library symbols for '
            'linux-gate.so',
            'Do you need "set solib-search-path" or '
            '"set sysroot"?',
195
            'warning: Source file is more recent than executable.',
196
            # Issue #19753: missing symbols on System Z
197
            'Missing separate debuginfo for ',
198
            'Try: zypper install -C ',
199 200 201 202
            )
        for line in errlines:
            if not line.startswith(ignore_patterns):
                unexpected_errlines.append(line)
203 204

        # Ensure no unexpected error messages:
205
        self.assertEqual(unexpected_errlines, [])
206 207 208 209 210 211
        return out

    def get_gdb_repr(self, source,
                     cmds_after_breakpoint=None,
                     import_site=False):
        # Given an input python source representation of data,
212 213
        # run "python -c'id(DATA)'" under gdb with a breakpoint on
        # builtin_id and scrape out gdb's representation of the "op"
214 215
        # parameter, and verify that the gdb displays the same string
        #
216 217
        # Verify that the gdb displays the expected string
        #
218 219
        # For a nested structure, the first time we hit the breakpoint will
        # give us the top-level structure
220 221 222 223 224

        # NOTE: avoid decoding too much of the traceback as some
        # undecodable characters may lurk there in optimized mode
        # (issue #19743).
        cmds_after_breakpoint = cmds_after_breakpoint or ["backtrace 1"]
225
        gdb_output = self.get_stack_trace(source, breakpoint=BREAKPOINT_FN,
226 227 228 229 230
                                          cmds_after_breakpoint=cmds_after_breakpoint,
                                          import_site=import_site)
        # gdb can insert additional '\n' and space characters in various places
        # in its output, depending on the width of the terminal it's connected
        # to (using its "wrap_here" function)
231
        m = re.match('.*#0\s+builtin_id\s+\(self\=.*,\s+v=\s*(.*?)\)\s+at\s+\S*Python/bltinmodule.c.*',
232 233 234 235 236 237 238
                     gdb_output, re.DOTALL)
        if not m:
            self.fail('Unexpected gdb output: %r\n%s' % (gdb_output, gdb_output))
        return m.group(1), gdb_output

    def assertEndsWith(self, actual, exp_end):
        '''Ensure that the given "actual" string ends with "exp_end"'''
239 240
        self.assertTrue(actual.endswith(exp_end),
                        msg='%r did not end with %r' % (actual, exp_end))
241 242 243

    def assertMultilineMatches(self, actual, pattern):
        m = re.match(pattern, actual, re.DOTALL)
244 245
        if not m:
            self.fail(msg='%r did not match %r' % (actual, pattern))
246

247 248 249
    def get_sample_script(self):
        return findfile('gdb_sample.py')

250 251
class PrettyPrintTests(DebuggerTests):
    def test_getting_backtrace(self):
252 253
        gdb_output = self.get_stack_trace('id(42)')
        self.assertTrue(BREAKPOINT_FN in gdb_output)
254

255
    def assertGdbRepr(self, val, exp_repr=None):
256 257
        # Ensure that gdb's rendering of the value in a debugged process
        # matches repr(value) in this process:
258
        gdb_repr, gdb_output = self.get_gdb_repr('id(' + ascii(val) + ')')
259
        if not exp_repr:
260
            exp_repr = repr(val)
261 262 263
        self.assertEqual(gdb_repr, exp_repr,
                         ('%r did not equal expected %r; full output was:\n%s'
                          % (gdb_repr, exp_repr, gdb_output)))
264 265

    def test_int(self):
266
        'Verify the pretty-printing of various int values'
267 268 269 270 271
        self.assertGdbRepr(42)
        self.assertGdbRepr(0)
        self.assertGdbRepr(-7)
        self.assertGdbRepr(1000000000000)
        self.assertGdbRepr(-1000000000000000)
272 273 274

    def test_singletons(self):
        'Verify the pretty-printing of True, False and None'
275 276 277
        self.assertGdbRepr(True)
        self.assertGdbRepr(False)
        self.assertGdbRepr(None)
278 279 280

    def test_dicts(self):
        'Verify the pretty-printing of dictionaries'
281 282 283
        self.assertGdbRepr({})
        self.assertGdbRepr({'foo': 'bar'}, "{'foo': 'bar'}")
        self.assertGdbRepr({'foo': 'bar', 'douglas': 42}, "{'douglas': 42, 'foo': 'bar'}")
284 285 286

    def test_lists(self):
        'Verify the pretty-printing of lists'
287 288
        self.assertGdbRepr([])
        self.assertGdbRepr(list(range(5)))
289 290 291 292 293 294 295 296 297 298 299 300 301 302

    def test_bytes(self):
        'Verify the pretty-printing of bytes'
        self.assertGdbRepr(b'')
        self.assertGdbRepr(b'And now for something hopefully the same')
        self.assertGdbRepr(b'string with embedded NUL here \0 and then some more text')
        self.assertGdbRepr(b'this is a tab:\t'
                           b' this is a slash-N:\n'
                           b' this is a slash-R:\r'
                           )

        self.assertGdbRepr(b'this is byte 255:\xff and byte 128:\x80')

        self.assertGdbRepr(bytes([b for b in range(255)]))
303 304

    def test_strings(self):
305
        'Verify the pretty-printing of unicode strings'
306 307 308 309 310 311
        encoding = locale.getpreferredencoding()
        def check_repr(text):
            try:
                text.encode(encoding)
                printable = True
            except UnicodeEncodeError:
Antoine Pitrou's avatar
Antoine Pitrou committed
312
                self.assertGdbRepr(text, ascii(text))
313 314 315
            else:
                self.assertGdbRepr(text)

316 317 318 319 320 321
        self.assertGdbRepr('')
        self.assertGdbRepr('And now for something hopefully the same')
        self.assertGdbRepr('string with embedded NUL here \0 and then some more text')

        # Test printing a single character:
        #    U+2620 SKULL AND CROSSBONES
322
        check_repr('\u2620')
323 324 325 326

        # Test printing a Japanese unicode string
        # (I believe this reads "mojibake", using 3 characters from the CJK
        # Unified Ideographs area, followed by U+3051 HIRAGANA LETTER KE)
327
        check_repr('\u6587\u5b57\u5316\u3051')
328 329 330 331 332 333

        # Test a character outside the BMP:
        #    U+1D121 MUSICAL SYMBOL C CLEF
        # This is:
        # UTF-8: 0xF0 0x9D 0x84 0xA1
        # UTF-16: 0xD834 0xDD21
334
        check_repr(chr(0x1D121))
335 336 337

    def test_tuples(self):
        'Verify the pretty-printing of tuples'
338
        self.assertGdbRepr(tuple(), '()')
339 340
        self.assertGdbRepr((1,), '(1,)')
        self.assertGdbRepr(('foo', 'bar', 'baz'))
341 342 343

    def test_sets(self):
        'Verify the pretty-printing of sets'
344 345
        if (gdb_major_version, gdb_minor_version) < (7, 3):
            self.skipTest("pretty-printing of sets needs gdb 7.3 or later")
346 347 348
        self.assertGdbRepr(set(), 'set()')
        self.assertGdbRepr(set(['a', 'b']), "{'a', 'b'}")
        self.assertGdbRepr(set([4, 5, 6]), "{4, 5, 6}")
349

350
        # Ensure that we handle sets containing the "dummy" key value,
351 352
        # which happens on deletion:
        gdb_repr, gdb_output = self.get_gdb_repr('''s = set(['a','b'])
353
s.remove('a')
354
id(s)''')
355
        self.assertEqual(gdb_repr, "{'b'}")
356 357 358

    def test_frozensets(self):
        'Verify the pretty-printing of frozensets'
359 360
        if (gdb_major_version, gdb_minor_version) < (7, 3):
            self.skipTest("pretty-printing of frozensets needs gdb 7.3 or later")
361 362 363
        self.assertGdbRepr(frozenset(), 'frozenset()')
        self.assertGdbRepr(frozenset(['a', 'b']), "frozenset({'a', 'b'})")
        self.assertGdbRepr(frozenset([4, 5, 6]), "frozenset({4, 5, 6})")
364 365 366 367 368 369

    def test_exceptions(self):
        # Test a RuntimeError
        gdb_repr, gdb_output = self.get_gdb_repr('''
try:
    raise RuntimeError("I am an error")
370 371
except RuntimeError as e:
    id(e)
372
''')
373 374
        self.assertEqual(gdb_repr,
                         "RuntimeError('I am an error',)")
375 376 377 378 379 380


        # Test division by zero:
        gdb_repr, gdb_output = self.get_gdb_repr('''
try:
    a = 1 / 0
381 382
except ZeroDivisionError as e:
    id(e)
383
''')
384 385
        self.assertEqual(gdb_repr,
                         "ZeroDivisionError('division by zero',)")
386 387 388 389

    def test_modern_class(self):
        'Verify the pretty-printing of new-style class instances'
        gdb_repr, gdb_output = self.get_gdb_repr('''
390
class Foo:
391 392 393
    pass
foo = Foo()
foo.an_int = 42
394
id(foo)''')
395
        m = re.match(r'<Foo\(an_int=42\) at remote 0x-?[0-9a-f]+>', gdb_repr)
396 397 398 399 400 401 402 403 404 405 406
        self.assertTrue(m,
                        msg='Unexpected new-style class rendering %r' % gdb_repr)

    def test_subclassing_list(self):
        'Verify the pretty-printing of an instance of a list subclass'
        gdb_repr, gdb_output = self.get_gdb_repr('''
class Foo(list):
    pass
foo = Foo()
foo += [1, 2, 3]
foo.an_int = 42
407
id(foo)''')
408
        m = re.match(r'<Foo\(an_int=42\) at remote 0x-?[0-9a-f]+>', gdb_repr)
409

410 411 412 413 414 415 416 417 418 419 420 421
        self.assertTrue(m,
                        msg='Unexpected new-style class rendering %r' % gdb_repr)

    def test_subclassing_tuple(self):
        'Verify the pretty-printing of an instance of a tuple subclass'
        # This should exercise the negative tp_dictoffset code in the
        # new-style class support
        gdb_repr, gdb_output = self.get_gdb_repr('''
class Foo(tuple):
    pass
foo = Foo((1, 2, 3))
foo.an_int = 42
422
id(foo)''')
423
        m = re.match(r'<Foo\(an_int=42\) at remote 0x-?[0-9a-f]+>', gdb_repr)
424

425 426 427
        self.assertTrue(m,
                        msg='Unexpected new-style class rendering %r' % gdb_repr)

428
    def assertSane(self, source, corruption, exprepr=None):
429 430 431 432 433 434 435 436 437 438 439 440 441
        '''Run Python under gdb, corrupting variables in the inferior process
        immediately before taking a backtrace.

        Verify that the variable's representation is the expected failsafe
        representation'''
        if corruption:
            cmds_after_breakpoint=[corruption, 'backtrace']
        else:
            cmds_after_breakpoint=['backtrace']

        gdb_repr, gdb_output = \
            self.get_gdb_repr(source,
                              cmds_after_breakpoint=cmds_after_breakpoint)
442 443
        if exprepr:
            if gdb_repr == exprepr:
444 445 446 447
                # gdb managed to print the value in spite of the corruption;
                # this is good (see http://bugs.python.org/issue8330)
                return

448 449
        # Match anything for the type name; 0xDEADBEEF could point to
        # something arbitrary (see  http://bugs.python.org/issue8330)
450
        pattern = '<.* at remote 0x-?[0-9a-f]+>'
451 452 453 454 455

        m = re.match(pattern, gdb_repr)
        if not m:
            self.fail('Unexpected gdb representation: %r\n%s' % \
                          (gdb_repr, gdb_output))
456 457 458 459

    def test_NULL_ptr(self):
        'Ensure that a NULL PyObject* is handled gracefully'
        gdb_repr, gdb_output = (
460 461
            self.get_gdb_repr('id(42)',
                              cmds_after_breakpoint=['set variable v=0',
462 463 464
                                                     'backtrace'])
            )

465
        self.assertEqual(gdb_repr, '0x0')
466 467 468

    def test_NULL_ob_type(self):
        'Ensure that a PyObject* with NULL ob_type is handled gracefully'
469 470
        self.assertSane('id(42)',
                        'set v->ob_type=0')
471 472 473

    def test_corrupt_ob_type(self):
        'Ensure that a PyObject* with a corrupt ob_type is handled gracefully'
474 475 476
        self.assertSane('id(42)',
                        'set v->ob_type=0xDEADBEEF',
                        exprepr='42')
477 478 479

    def test_corrupt_tp_flags(self):
        'Ensure that a PyObject* with a type with corrupt tp_flags is handled'
480 481 482
        self.assertSane('id(42)',
                        'set v->ob_type->tp_flags=0x0',
                        exprepr='42')
483 484 485

    def test_corrupt_tp_name(self):
        'Ensure that a PyObject* with a type with corrupt tp_name is handled'
486 487 488
        self.assertSane('id(42)',
                        'set v->ob_type->tp_name=0xDEADBEEF',
                        exprepr='42')
489 490 491 492 493

    def test_builtins_help(self):
        'Ensure that the new-style class _Helper in site.py can be handled'
        # (this was the issue causing tracebacks in
        #  http://bugs.python.org/issue8032#msg100537 )
494
        gdb_repr, gdb_output = self.get_gdb_repr('id(__builtins__.help)', import_site=True)
495

496
        m = re.match(r'<_Helper at remote 0x-?[0-9a-f]+>', gdb_repr)
497 498 499 500 501 502 503
        self.assertTrue(m,
                        msg='Unexpected rendering %r' % gdb_repr)

    def test_selfreferential_list(self):
        '''Ensure that a reference loop involving a list doesn't lead proxyval
        into an infinite loop:'''
        gdb_repr, gdb_output = \
504
            self.get_gdb_repr("a = [3, 4, 5] ; a.append(a) ; id(a)")
505
        self.assertEqual(gdb_repr, '[3, 4, 5, [...]]')
506 507

        gdb_repr, gdb_output = \
508
            self.get_gdb_repr("a = [3, 4, 5] ; b = [a] ; a.append(b) ; id(a)")
509
        self.assertEqual(gdb_repr, '[3, 4, 5, [[...]]]')
510 511 512 513 514

    def test_selfreferential_dict(self):
        '''Ensure that a reference loop involving a dict doesn't lead proxyval
        into an infinite loop:'''
        gdb_repr, gdb_output = \
515
            self.get_gdb_repr("a = {} ; b = {'bar':a} ; a['foo'] = b ; id(a)")
516

517
        self.assertEqual(gdb_repr, "{'foo': {'bar': {...}}}")
518 519 520 521 522 523 524 525

    def test_selfreferential_old_style_instance(self):
        gdb_repr, gdb_output = \
            self.get_gdb_repr('''
class Foo:
    pass
foo = Foo()
foo.an_attr = foo
526
id(foo)''')
527
        self.assertTrue(re.match('<Foo\(an_attr=<\.\.\.>\) at remote 0x-?[0-9a-f]+>',
528 529 530 531 532 533 534 535 536 537 538
                                 gdb_repr),
                        'Unexpected gdb representation: %r\n%s' % \
                            (gdb_repr, gdb_output))

    def test_selfreferential_new_style_instance(self):
        gdb_repr, gdb_output = \
            self.get_gdb_repr('''
class Foo(object):
    pass
foo = Foo()
foo.an_attr = foo
539
id(foo)''')
540
        self.assertTrue(re.match('<Foo\(an_attr=<\.\.\.>\) at remote 0x-?[0-9a-f]+>',
541 542 543 544 545 546 547 548 549 550 551 552
                                 gdb_repr),
                        'Unexpected gdb representation: %r\n%s' % \
                            (gdb_repr, gdb_output))

        gdb_repr, gdb_output = \
            self.get_gdb_repr('''
class Foo(object):
    pass
a = Foo()
b = Foo()
a.an_attr = b
b.an_attr = a
553
id(a)''')
554
        self.assertTrue(re.match('<Foo\(an_attr=<Foo\(an_attr=<\.\.\.>\) at remote 0x-?[0-9a-f]+>\) at remote 0x-?[0-9a-f]+>',
555 556 557 558 559 560
                                 gdb_repr),
                        'Unexpected gdb representation: %r\n%s' % \
                            (gdb_repr, gdb_output))

    def test_truncation(self):
        'Verify that very long output is truncated'
561
        gdb_repr, gdb_output = self.get_gdb_repr('id(list(range(1000)))')
562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585
        self.assertEqual(gdb_repr,
                         "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, "
                         "14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, "
                         "27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, "
                         "40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, "
                         "53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, "
                         "66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, "
                         "79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, "
                         "92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, "
                         "104, 105, 106, 107, 108, 109, 110, 111, 112, 113, "
                         "114, 115, 116, 117, 118, 119, 120, 121, 122, 123, "
                         "124, 125, 126, 127, 128, 129, 130, 131, 132, 133, "
                         "134, 135, 136, 137, 138, 139, 140, 141, 142, 143, "
                         "144, 145, 146, 147, 148, 149, 150, 151, 152, 153, "
                         "154, 155, 156, 157, 158, 159, 160, 161, 162, 163, "
                         "164, 165, 166, 167, 168, 169, 170, 171, 172, 173, "
                         "174, 175, 176, 177, 178, 179, 180, 181, 182, 183, "
                         "184, 185, 186, 187, 188, 189, 190, 191, 192, 193, "
                         "194, 195, 196, 197, 198, 199, 200, 201, 202, 203, "
                         "204, 205, 206, 207, 208, 209, 210, 211, 212, 213, "
                         "214, 215, 216, 217, 218, 219, 220, 221, 222, 223, "
                         "224, 225, 226...(truncated)")
        self.assertEqual(len(gdb_repr),
                         1024 + len('...(truncated)'))
586 587

    def test_builtin_method(self):
588
        gdb_repr, gdb_output = self.get_gdb_repr('import sys; id(sys.stdout.readlines)')
589
        self.assertTrue(re.match('<built-in method readlines of _io.TextIOWrapper object at remote 0x-?[0-9a-f]+>',
590 591 592 593 594 595 596 597 598 599
                                 gdb_repr),
                        'Unexpected gdb representation: %r\n%s' % \
                            (gdb_repr, gdb_output))

    def test_frames(self):
        gdb_output = self.get_stack_trace('''
def foo(a, b, c):
    pass

foo(3, 4, 5)
600 601 602
id(foo.__code__)''',
                                          breakpoint='builtin_id',
                                          cmds_after_breakpoint=['print (PyFrameObject*)(((PyCodeObject*)v)->co_zombieframe)']
603
                                          )
604
        self.assertTrue(re.match('.*\s+\$1 =\s+Frame 0x-?[0-9a-f]+, for file <string>, line 3, in foo \(\)\s+.*',
605 606 607 608
                                 gdb_output,
                                 re.DOTALL),
                        'Unexpected gdb representation: %r\n%s' % (gdb_output, gdb_output))

609 610
@unittest.skipIf(python_is_optimized(),
                 "Python was compiled with optimizations")
611 612 613 614 615 616
class PyListTests(DebuggerTests):
    def assertListing(self, expected, actual):
        self.assertEndsWith(actual, expected)

    def test_basic_command(self):
        'Verify that the "py-list" command works'
617
        bt = self.get_stack_trace(script=self.get_sample_script(),
618 619
                                  cmds_after_breakpoint=['py-list'])

620 621 622 623 624
        self.assertListing('   5    \n'
                           '   6    def bar(a, b, c):\n'
                           '   7        baz(a, b, c)\n'
                           '   8    \n'
                           '   9    def baz(*args):\n'
625
                           ' >10        id(42)\n'
626 627 628
                           '  11    \n'
                           '  12    foo(1, 2, 3)\n',
                           bt)
629 630 631

    def test_one_abs_arg(self):
        'Verify the "py-list" command with one absolute argument'
632
        bt = self.get_stack_trace(script=self.get_sample_script(),
633 634
                                  cmds_after_breakpoint=['py-list 9'])

635
        self.assertListing('   9    def baz(*args):\n'
636
                           ' >10        id(42)\n'
637 638 639
                           '  11    \n'
                           '  12    foo(1, 2, 3)\n',
                           bt)
640 641 642

    def test_two_abs_args(self):
        'Verify the "py-list" command with two absolute arguments'
643
        bt = self.get_stack_trace(script=self.get_sample_script(),
644 645
                                  cmds_after_breakpoint=['py-list 1,3'])

646 647 648 649
        self.assertListing('   1    # Sample script for use by test_gdb.py\n'
                           '   2    \n'
                           '   3    def foo(a, b, c):\n',
                           bt)
650 651

class StackNavigationTests(DebuggerTests):
652
    @unittest.skipUnless(HAS_PYUP_PYDOWN, "test requires py-up/py-down commands")
653 654
    @unittest.skipIf(python_is_optimized(),
                     "Python was compiled with optimizations")
655 656
    def test_pyup_command(self):
        'Verify that the "py-up" command works'
657
        bt = self.get_stack_trace(script=self.get_sample_script(),
658 659 660
                                  cmds_after_breakpoint=['py-up'])
        self.assertMultilineMatches(bt,
                                    r'''^.*
661
#[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 7, in bar \(a=1, b=2, c=3\)
662 663 664
    baz\(a, b, c\)
$''')

665
    @unittest.skipUnless(HAS_PYUP_PYDOWN, "test requires py-up/py-down commands")
666 667
    def test_down_at_bottom(self):
        'Verify handling of "py-down" at the bottom of the stack'
668
        bt = self.get_stack_trace(script=self.get_sample_script(),
669 670 671 672
                                  cmds_after_breakpoint=['py-down'])
        self.assertEndsWith(bt,
                            'Unable to find a newer python frame\n')

673
    @unittest.skipUnless(HAS_PYUP_PYDOWN, "test requires py-up/py-down commands")
674 675
    def test_up_at_top(self):
        'Verify handling of "py-up" at the top of the stack'
676
        bt = self.get_stack_trace(script=self.get_sample_script(),
677 678 679 680
                                  cmds_after_breakpoint=['py-up'] * 4)
        self.assertEndsWith(bt,
                            'Unable to find an older python frame\n')

681
    @unittest.skipUnless(HAS_PYUP_PYDOWN, "test requires py-up/py-down commands")
682 683
    @unittest.skipIf(python_is_optimized(),
                     "Python was compiled with optimizations")
684 685
    def test_up_then_down(self):
        'Verify "py-up" followed by "py-down"'
686
        bt = self.get_stack_trace(script=self.get_sample_script(),
687 688 689
                                  cmds_after_breakpoint=['py-up', 'py-down'])
        self.assertMultilineMatches(bt,
                                    r'''^.*
690
#[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 7, in bar \(a=1, b=2, c=3\)
691
    baz\(a, b, c\)
692
#[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 10, in baz \(args=\(1, 2, 3\)\)
693
    id\(42\)
694 695 696
$''')

class PyBtTests(DebuggerTests):
697 698
    @unittest.skipIf(python_is_optimized(),
                     "Python was compiled with optimizations")
699
    def test_bt(self):
700
        'Verify that the "py-bt" command works'
701
        bt = self.get_stack_trace(script=self.get_sample_script(),
702 703 704
                                  cmds_after_breakpoint=['py-bt'])
        self.assertMultilineMatches(bt,
                                    r'''^.*
705 706 707 708 709 710 711 712 713 714 715
Traceback \(most recent call first\):
  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, b, c\)
  File ".*gdb_sample.py", line 12, in <module>
    foo\(1, 2, 3\)
''')

716 717
    @unittest.skipIf(python_is_optimized(),
                     "Python was compiled with optimizations")
718 719 720 721 722 723
    def test_bt_full(self):
        'Verify that the "py-bt-full" command works'
        bt = self.get_stack_trace(script=self.get_sample_script(),
                                  cmds_after_breakpoint=['py-bt-full'])
        self.assertMultilineMatches(bt,
                                    r'''^.*
724
#[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 7, in bar \(a=1, b=2, c=3\)
725
    baz\(a, b, c\)
726
#[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 4, in foo \(a=1, b=2, c=3\)
727
    bar\(a, b, c\)
728
#[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 12, in <module> \(\)
729
    foo\(1, 2, 3\)
730 731
''')

732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823
    @unittest.skipUnless(_thread,
                         "Python was compiled without thread support")
    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
    @unittest.skipUnless(_thread,
                         "Python was compiled without thread support")
    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")
    # 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
    @unittest.skipUnless(_thread,
                         "Python was compiled without thread support")
    def test_pycfunction(self):
        'Verify that "py-bt" displays invocations of PyCFunction instances'
        cmd = ('from time import sleep\n'
               'def foo():\n'
               '    sleep(1)\n'
               'def bar():\n'
               '    foo()\n'
               'bar()\n')
        # Verify with "py-bt":
        gdb_output = self.get_stack_trace(cmd,
                                          breakpoint='time_sleep',
                                          cmds_after_breakpoint=['bt', 'py-bt'],
                                          )
        self.assertIn('<built-in method sleep', gdb_output)

        # Verify with "py-bt-full":
        gdb_output = self.get_stack_trace(cmd,
                                          breakpoint='time_sleep',
                                          cmds_after_breakpoint=['py-bt-full'],
                                          )
        self.assertIn('#0 <built-in method sleep', gdb_output)


824
class PyPrintTests(DebuggerTests):
825 826
    @unittest.skipIf(python_is_optimized(),
                     "Python was compiled with optimizations")
827 828
    def test_basic_command(self):
        'Verify that the "py-print" command works'
829
        bt = self.get_stack_trace(script=self.get_sample_script(),
830 831 832 833
                                  cmds_after_breakpoint=['py-print args'])
        self.assertMultilineMatches(bt,
                                    r".*\nlocal 'args' = \(1, 2, 3\)\n.*")

834 835
    @unittest.skipIf(python_is_optimized(),
                     "Python was compiled with optimizations")
836
    @unittest.skipUnless(HAS_PYUP_PYDOWN, "test requires py-up/py-down commands")
837
    def test_print_after_up(self):
838
        bt = self.get_stack_trace(script=self.get_sample_script(),
839 840 841 842
                                  cmds_after_breakpoint=['py-up', 'py-print c', 'py-print b', 'py-print a'])
        self.assertMultilineMatches(bt,
                                    r".*\nlocal 'c' = 3\nlocal 'b' = 2\nlocal 'a' = 1\n.*")

843 844
    @unittest.skipIf(python_is_optimized(),
                     "Python was compiled with optimizations")
845
    def test_printing_global(self):
846
        bt = self.get_stack_trace(script=self.get_sample_script(),
847 848 849 850
                                  cmds_after_breakpoint=['py-print __name__'])
        self.assertMultilineMatches(bt,
                                    r".*\nglobal '__name__' = '__main__'\n.*")

851 852
    @unittest.skipIf(python_is_optimized(),
                     "Python was compiled with optimizations")
853
    def test_printing_builtin(self):
854
        bt = self.get_stack_trace(script=self.get_sample_script(),
855 856
                                  cmds_after_breakpoint=['py-print len'])
        self.assertMultilineMatches(bt,
857
                                    r".*\nbuiltin 'len' = <built-in method len of module object at remote 0x-?[0-9a-f]+>\n.*")
858 859

class PyLocalsTests(DebuggerTests):
860 861
    @unittest.skipIf(python_is_optimized(),
                     "Python was compiled with optimizations")
862
    def test_basic_command(self):
863
        bt = self.get_stack_trace(script=self.get_sample_script(),
864 865 866 867
                                  cmds_after_breakpoint=['py-locals'])
        self.assertMultilineMatches(bt,
                                    r".*\nargs = \(1, 2, 3\)\n.*")

868
    @unittest.skipUnless(HAS_PYUP_PYDOWN, "test requires py-up/py-down commands")
869 870
    @unittest.skipIf(python_is_optimized(),
                     "Python was compiled with optimizations")
871
    def test_locals_after_up(self):
872
        bt = self.get_stack_trace(script=self.get_sample_script(),
873 874 875 876 877
                                  cmds_after_breakpoint=['py-up', 'py-locals'])
        self.assertMultilineMatches(bt,
                                    r".*\na = 1\nb = 2\nc = 3\n.*")

def test_main():
878 879 880 881
    if support.verbose:
        print("GDB version:")
        for line in os.fsdecode(gdb_version).splitlines():
            print(" " * 4 + line)
882 883 884 885 886 887
    run_unittest(PrettyPrintTests,
                 PyListTests,
                 StackNavigationTests,
                 PyBtTests,
                 PyPrintTests,
                 PyLocalsTests
888 889 890 891
                 )

if __name__ == "__main__":
    test_main()