python (3.12.0)

(root)/
lib/
python3.12/
test/
test_gdb.py
       1  # Verify that gdb can pretty-print the various PyObject* types
       2  #
       3  # The code for testing gdb was adapted from similar work in Unladen Swallow's
       4  # Lib/test/test_jit_gdb.py
       5  
       6  import os
       7  import platform
       8  import re
       9  import subprocess
      10  import sys
      11  import sysconfig
      12  import textwrap
      13  import unittest
      14  
      15  from test import support
      16  from test.support import findfile, python_is_optimized
      17  
      18  def get_gdb_version():
      19      try:
      20          cmd = ["gdb", "-nx", "--version"]
      21          proc = subprocess.Popen(cmd,
      22                                  stdout=subprocess.PIPE,
      23                                  stderr=subprocess.PIPE,
      24                                  universal_newlines=True)
      25          with proc:
      26              version, stderr = proc.communicate()
      27  
      28          if proc.returncode:
      29              raise Exception(f"Command {' '.join(cmd)!r} failed "
      30                              f"with exit code {proc.returncode}: "
      31                              f"stdout={version!r} stderr={stderr!r}")
      32      except OSError:
      33          # This is what "no gdb" looks like.  There may, however, be other
      34          # errors that manifest this way too.
      35          raise unittest.SkipTest("Couldn't find gdb on the path")
      36  
      37      # Regex to parse:
      38      # 'GNU gdb (GDB; SUSE Linux Enterprise 12) 7.7\n' -> 7.7
      39      # 'GNU gdb (GDB) Fedora 7.9.1-17.fc22\n' -> 7.9
      40      # 'GNU gdb 6.1.1 [FreeBSD]\n' -> 6.1
      41      # 'GNU gdb (GDB) Fedora (7.5.1-37.fc18)\n' -> 7.5
      42      # 'HP gdb 6.7 for HP Itanium (32 or 64 bit) and target HP-UX 11iv2 and 11iv3.\n' -> 6.7
      43      match = re.search(r"^(?:GNU|HP) gdb.*?\b(\d+)\.(\d+)", version)
      44      if match is None:
      45          raise Exception("unable to parse GDB version: %r" % version)
      46      return (version, int(match.group(1)), int(match.group(2)))
      47  
      48  gdb_version, gdb_major_version, gdb_minor_version = get_gdb_version()
      49  if gdb_major_version < 7:
      50      raise unittest.SkipTest("gdb versions before 7.0 didn't support python "
      51                              "embedding. Saw %s.%s:\n%s"
      52                              % (gdb_major_version, gdb_minor_version,
      53                                 gdb_version))
      54  
      55  if not sysconfig.is_python_build():
      56      raise unittest.SkipTest("test_gdb only works on source builds at the moment.")
      57  
      58  if ((sysconfig.get_config_var('PGO_PROF_USE_FLAG') or 'xxx') in
      59      (sysconfig.get_config_var('PY_CORE_CFLAGS') or '')):
      60      raise unittest.SkipTest("test_gdb is not reliable on PGO builds")
      61  
      62  # Location of custom hooks file in a repository checkout.
      63  checkout_hook_path = os.path.join(os.path.dirname(sys.executable),
      64                                    'python-gdb.py')
      65  
      66  PYTHONHASHSEED = '123'
      67  
      68  
      69  def cet_protection():
      70      cflags = sysconfig.get_config_var('CFLAGS')
      71      if not cflags:
      72          return False
      73      flags = cflags.split()
      74      # True if "-mcet -fcf-protection" options are found, but false
      75      # if "-fcf-protection=none" or "-fcf-protection=return" is found.
      76      return (('-mcet' in flags)
      77              and any((flag.startswith('-fcf-protection')
      78                       and not flag.endswith(("=none", "=return")))
      79                      for flag in flags))
      80  
      81  # Control-flow enforcement technology
      82  CET_PROTECTION = cet_protection()
      83  
      84  
      85  def run_gdb(*args, **env_vars):
      86      """Runs gdb in --batch mode with the additional arguments given by *args.
      87  
      88      Returns its (stdout, stderr) decoded from utf-8 using the replace handler.
      89      """
      90      if env_vars:
      91          env = os.environ.copy()
      92          env.update(env_vars)
      93      else:
      94          env = None
      95      # -nx: Do not execute commands from any .gdbinit initialization files
      96      #      (issue #22188)
      97      base_cmd = ('gdb', '--batch', '-nx')
      98      if (gdb_major_version, gdb_minor_version) >= (7, 4):
      99          base_cmd += ('-iex', 'add-auto-load-safe-path ' + checkout_hook_path)
     100      proc = subprocess.Popen(base_cmd + args,
     101                              # Redirect stdin to prevent GDB from messing with
     102                              # the terminal settings
     103                              stdin=subprocess.PIPE,
     104                              stdout=subprocess.PIPE,
     105                              stderr=subprocess.PIPE,
     106                              env=env)
     107      with proc:
     108          out, err = proc.communicate()
     109      return out.decode('utf-8', 'replace'), err.decode('utf-8', 'replace')
     110  
     111  # Verify that "gdb" was built with the embedded python support enabled:
     112  gdbpy_version, _ = run_gdb("--eval-command=python import sys; print(sys.version_info)")
     113  if not gdbpy_version:
     114      raise unittest.SkipTest("gdb not built with embedded python support")
     115  
     116  if "major=2" in gdbpy_version:
     117      raise unittest.SkipTest("gdb built with Python 2")
     118  
     119  # Verify that "gdb" can load our custom hooks, as OS security settings may
     120  # disallow this without a customized .gdbinit.
     121  _, gdbpy_errors = run_gdb('--args', sys.executable)
     122  if "auto-loading has been declined" in gdbpy_errors:
     123      msg = "gdb security settings prevent use of custom hooks: "
     124      raise unittest.SkipTest(msg + gdbpy_errors.rstrip())
     125  
     126  def gdb_has_frame_select():
     127      # Does this build of gdb have gdb.Frame.select ?
     128      stdout, _ = run_gdb("--eval-command=python print(dir(gdb.Frame))")
     129      m = re.match(r'.*\[(.*)\].*', stdout)
     130      if not m:
     131          raise unittest.SkipTest("Unable to parse output from gdb.Frame.select test")
     132      gdb_frame_dir = m.group(1).split(', ')
     133      return "'select'" in gdb_frame_dir
     134  
     135  HAS_PYUP_PYDOWN = gdb_has_frame_select()
     136  
     137  BREAKPOINT_FN='builtin_id'
     138  
     139  @unittest.skipIf(support.PGO, "not useful for PGO")
     140  class ESC[4;38;5;81mDebuggerTests(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
     141  
     142      """Test that the debugger can debug Python."""
     143  
     144      def get_stack_trace(self, source=None, script=None,
     145                          breakpoint=BREAKPOINT_FN,
     146                          cmds_after_breakpoint=None,
     147                          import_site=False,
     148                          ignore_stderr=False):
     149          '''
     150          Run 'python -c SOURCE' under gdb with a breakpoint.
     151  
     152          Support injecting commands after the breakpoint is reached
     153  
     154          Returns the stdout from gdb
     155  
     156          cmds_after_breakpoint: if provided, a list of strings: gdb commands
     157          '''
     158          # We use "set breakpoint pending yes" to avoid blocking with a:
     159          #   Function "foo" not defined.
     160          #   Make breakpoint pending on future shared library load? (y or [n])
     161          # error, which typically happens python is dynamically linked (the
     162          # breakpoints of interest are to be found in the shared library)
     163          # When this happens, we still get:
     164          #   Function "textiowrapper_write" not defined.
     165          # emitted to stderr each time, alas.
     166  
     167          # Initially I had "--eval-command=continue" here, but removed it to
     168          # avoid repeated print breakpoints when traversing hierarchical data
     169          # structures
     170  
     171          # Generate a list of commands in gdb's language:
     172          commands = ['set breakpoint pending yes',
     173                      'break %s' % breakpoint,
     174  
     175                      # The tests assume that the first frame of printed
     176                      #  backtrace will not contain program counter,
     177                      #  that is however not guaranteed by gdb
     178                      #  therefore we need to use 'set print address off' to
     179                      #  make sure the counter is not there. For example:
     180                      # #0 in PyObject_Print ...
     181                      #  is assumed, but sometimes this can be e.g.
     182                      # #0 0x00003fffb7dd1798 in PyObject_Print ...
     183                      'set print address off',
     184  
     185                      'run']
     186  
     187          # GDB as of 7.4 onwards can distinguish between the
     188          # value of a variable at entry vs current value:
     189          #   http://sourceware.org/gdb/onlinedocs/gdb/Variables.html
     190          # which leads to the selftests failing with errors like this:
     191          #   AssertionError: 'v@entry=()' != '()'
     192          # Disable this:
     193          if (gdb_major_version, gdb_minor_version) >= (7, 4):
     194              commands += ['set print entry-values no']
     195  
     196          if cmds_after_breakpoint:
     197              if CET_PROTECTION:
     198                  # bpo-32962: When Python is compiled with -mcet
     199                  # -fcf-protection, function arguments are unusable before
     200                  # running the first instruction of the function entry point.
     201                  # The 'next' command makes the required first step.
     202                  commands += ['next']
     203              commands += cmds_after_breakpoint
     204          else:
     205              commands += ['backtrace']
     206  
     207          # print commands
     208  
     209          # Use "commands" to generate the arguments with which to invoke "gdb":
     210          args = ['--eval-command=%s' % cmd for cmd in commands]
     211          args += ["--args",
     212                   sys.executable]
     213          args.extend(subprocess._args_from_interpreter_flags())
     214  
     215          if not import_site:
     216              # -S suppresses the default 'import site'
     217              args += ["-S"]
     218  
     219          if source:
     220              args += ["-c", source]
     221          elif script:
     222              args += [script]
     223  
     224          # Use "args" to invoke gdb, capturing stdout, stderr:
     225          out, err = run_gdb(*args, PYTHONHASHSEED=PYTHONHASHSEED)
     226  
     227          if not ignore_stderr:
     228              for line in err.splitlines():
     229                  print(line, file=sys.stderr)
     230  
     231          # bpo-34007: Sometimes some versions of the shared libraries that
     232          # are part of the traceback are compiled in optimised mode and the
     233          # Program Counter (PC) is not present, not allowing gdb to walk the
     234          # frames back. When this happens, the Python bindings of gdb raise
     235          # an exception, making the test impossible to succeed.
     236          if "PC not saved" in err:
     237              raise unittest.SkipTest("gdb cannot walk the frame object"
     238                                      " because the Program Counter is"
     239                                      " not present")
     240  
     241          # bpo-40019: Skip the test if gdb failed to read debug information
     242          # because the Python binary is optimized.
     243          for pattern in (
     244              '(frame information optimized out)',
     245              'Unable to read information on python frame',
     246              # gh-91960: On Python built with "clang -Og", gdb gets
     247              # "frame=<optimized out>" for _PyEval_EvalFrameDefault() parameter
     248              '(unable to read python frame information)',
     249              # gh-104736: On Python built with "clang -Og" on ppc64le,
     250              # "py-bt" displays a truncated or not traceback, but "where"
     251              # logs this error message:
     252              'Backtrace stopped: frame did not save the PC',
     253              # gh-104736: When "bt" command displays something like:
     254              # "#1  0x0000000000000000 in ?? ()", the traceback is likely
     255              # truncated or wrong.
     256              ' ?? ()',
     257          ):
     258              if pattern in out:
     259                  raise unittest.SkipTest(f"{pattern!r} found in gdb output")
     260  
     261          return out
     262  
     263      def get_gdb_repr(self, source,
     264                       cmds_after_breakpoint=None,
     265                       import_site=False):
     266          # Given an input python source representation of data,
     267          # run "python -c'id(DATA)'" under gdb with a breakpoint on
     268          # builtin_id and scrape out gdb's representation of the "op"
     269          # parameter, and verify that the gdb displays the same string
     270          #
     271          # Verify that the gdb displays the expected string
     272          #
     273          # For a nested structure, the first time we hit the breakpoint will
     274          # give us the top-level structure
     275  
     276          # NOTE: avoid decoding too much of the traceback as some
     277          # undecodable characters may lurk there in optimized mode
     278          # (issue #19743).
     279          cmds_after_breakpoint = cmds_after_breakpoint or ["backtrace 1"]
     280          gdb_output = self.get_stack_trace(source, breakpoint=BREAKPOINT_FN,
     281                                            cmds_after_breakpoint=cmds_after_breakpoint,
     282                                            import_site=import_site)
     283          # gdb can insert additional '\n' and space characters in various places
     284          # in its output, depending on the width of the terminal it's connected
     285          # to (using its "wrap_here" function)
     286          m = re.search(
     287              # Match '#0 builtin_id(self=..., v=...)'
     288              r'#0\s+builtin_id\s+\(self\=.*,\s+v=\s*(.*?)?\)'
     289              # Match ' at Python/bltinmodule.c'.
     290              # bpo-38239: builtin_id() is defined in Python/bltinmodule.c,
     291              # but accept any "Directory\file.c" to support Link Time
     292              # Optimization (LTO).
     293              r'\s+at\s+\S*[A-Za-z]+/[A-Za-z0-9_-]+\.c',
     294              gdb_output, re.DOTALL)
     295          if not m:
     296              self.fail('Unexpected gdb output: %r\n%s' % (gdb_output, gdb_output))
     297          return m.group(1), gdb_output
     298  
     299      def assertEndsWith(self, actual, exp_end):
     300          '''Ensure that the given "actual" string ends with "exp_end"'''
     301          self.assertTrue(actual.endswith(exp_end),
     302                          msg='%r did not end with %r' % (actual, exp_end))
     303  
     304      def assertMultilineMatches(self, actual, pattern):
     305          m = re.match(pattern, actual, re.DOTALL)
     306          if not m:
     307              self.fail(msg='%r did not match %r' % (actual, pattern))
     308  
     309      def get_sample_script(self):
     310          return findfile('gdb_sample.py')
     311  
     312  class ESC[4;38;5;81mPrettyPrintTests(ESC[4;38;5;149mDebuggerTests):
     313      def test_getting_backtrace(self):
     314          gdb_output = self.get_stack_trace('id(42)')
     315          self.assertTrue(BREAKPOINT_FN in gdb_output)
     316  
     317      def assertGdbRepr(self, val, exp_repr=None):
     318          # Ensure that gdb's rendering of the value in a debugged process
     319          # matches repr(value) in this process:
     320          gdb_repr, gdb_output = self.get_gdb_repr('id(' + ascii(val) + ')')
     321          if not exp_repr:
     322              exp_repr = repr(val)
     323          self.assertEqual(gdb_repr, exp_repr,
     324                           ('%r did not equal expected %r; full output was:\n%s'
     325                            % (gdb_repr, exp_repr, gdb_output)))
     326  
     327      @support.requires_resource('cpu')
     328      def test_int(self):
     329          'Verify the pretty-printing of various int values'
     330          self.assertGdbRepr(42)
     331          self.assertGdbRepr(0)
     332          self.assertGdbRepr(-7)
     333          self.assertGdbRepr(1000000000000)
     334          self.assertGdbRepr(-1000000000000000)
     335  
     336      def test_singletons(self):
     337          'Verify the pretty-printing of True, False and None'
     338          self.assertGdbRepr(True)
     339          self.assertGdbRepr(False)
     340          self.assertGdbRepr(None)
     341  
     342      def test_dicts(self):
     343          'Verify the pretty-printing of dictionaries'
     344          self.assertGdbRepr({})
     345          self.assertGdbRepr({'foo': 'bar'}, "{'foo': 'bar'}")
     346          # Python preserves insertion order since 3.6
     347          self.assertGdbRepr({'foo': 'bar', 'douglas': 42}, "{'foo': 'bar', 'douglas': 42}")
     348  
     349      def test_lists(self):
     350          'Verify the pretty-printing of lists'
     351          self.assertGdbRepr([])
     352          self.assertGdbRepr(list(range(5)))
     353  
     354      @support.requires_resource('cpu')
     355      def test_bytes(self):
     356          'Verify the pretty-printing of bytes'
     357          self.assertGdbRepr(b'')
     358          self.assertGdbRepr(b'And now for something hopefully the same')
     359          self.assertGdbRepr(b'string with embedded NUL here \0 and then some more text')
     360          self.assertGdbRepr(b'this is a tab:\t'
     361                             b' this is a slash-N:\n'
     362                             b' this is a slash-R:\r'
     363                             )
     364  
     365          self.assertGdbRepr(b'this is byte 255:\xff and byte 128:\x80')
     366  
     367          self.assertGdbRepr(bytes([b for b in range(255)]))
     368  
     369      @support.requires_resource('cpu')
     370      def test_strings(self):
     371          'Verify the pretty-printing of unicode strings'
     372          # We cannot simply call locale.getpreferredencoding() here,
     373          # as GDB might have been linked against a different version
     374          # of Python with a different encoding and coercion policy
     375          # with respect to PEP 538 and PEP 540.
     376          out, err = run_gdb(
     377              '--eval-command',
     378              'python import locale; print(locale.getpreferredencoding())')
     379  
     380          encoding = out.rstrip()
     381          if err or not encoding:
     382              raise RuntimeError(
     383                  f'unable to determine the preferred encoding '
     384                  f'of embedded Python in GDB: {err}')
     385  
     386          def check_repr(text):
     387              try:
     388                  text.encode(encoding)
     389              except UnicodeEncodeError:
     390                  self.assertGdbRepr(text, ascii(text))
     391              else:
     392                  self.assertGdbRepr(text)
     393  
     394          self.assertGdbRepr('')
     395          self.assertGdbRepr('And now for something hopefully the same')
     396          self.assertGdbRepr('string with embedded NUL here \0 and then some more text')
     397  
     398          # Test printing a single character:
     399          #    U+2620 SKULL AND CROSSBONES
     400          check_repr('\u2620')
     401  
     402          # Test printing a Japanese unicode string
     403          # (I believe this reads "mojibake", using 3 characters from the CJK
     404          # Unified Ideographs area, followed by U+3051 HIRAGANA LETTER KE)
     405          check_repr('\u6587\u5b57\u5316\u3051')
     406  
     407          # Test a character outside the BMP:
     408          #    U+1D121 MUSICAL SYMBOL C CLEF
     409          # This is:
     410          # UTF-8: 0xF0 0x9D 0x84 0xA1
     411          # UTF-16: 0xD834 0xDD21
     412          check_repr(chr(0x1D121))
     413  
     414      def test_tuples(self):
     415          'Verify the pretty-printing of tuples'
     416          self.assertGdbRepr(tuple(), '()')
     417          self.assertGdbRepr((1,), '(1,)')
     418          self.assertGdbRepr(('foo', 'bar', 'baz'))
     419  
     420      @support.requires_resource('cpu')
     421      def test_sets(self):
     422          'Verify the pretty-printing of sets'
     423          if (gdb_major_version, gdb_minor_version) < (7, 3):
     424              self.skipTest("pretty-printing of sets needs gdb 7.3 or later")
     425          self.assertGdbRepr(set(), "set()")
     426          self.assertGdbRepr(set(['a']), "{'a'}")
     427          # PYTHONHASHSEED is need to get the exact frozenset item order
     428          if not sys.flags.ignore_environment:
     429              self.assertGdbRepr(set(['a', 'b']), "{'a', 'b'}")
     430              self.assertGdbRepr(set([4, 5, 6]), "{4, 5, 6}")
     431  
     432          # Ensure that we handle sets containing the "dummy" key value,
     433          # which happens on deletion:
     434          gdb_repr, gdb_output = self.get_gdb_repr('''s = set(['a','b'])
     435  s.remove('a')
     436  id(s)''')
     437          self.assertEqual(gdb_repr, "{'b'}")
     438  
     439      @support.requires_resource('cpu')
     440      def test_frozensets(self):
     441          'Verify the pretty-printing of frozensets'
     442          if (gdb_major_version, gdb_minor_version) < (7, 3):
     443              self.skipTest("pretty-printing of frozensets needs gdb 7.3 or later")
     444          self.assertGdbRepr(frozenset(), "frozenset()")
     445          self.assertGdbRepr(frozenset(['a']), "frozenset({'a'})")
     446          # PYTHONHASHSEED is need to get the exact frozenset item order
     447          if not sys.flags.ignore_environment:
     448              self.assertGdbRepr(frozenset(['a', 'b']), "frozenset({'a', 'b'})")
     449              self.assertGdbRepr(frozenset([4, 5, 6]), "frozenset({4, 5, 6})")
     450  
     451      def test_exceptions(self):
     452          # Test a RuntimeError
     453          gdb_repr, gdb_output = self.get_gdb_repr('''
     454  try:
     455      raise RuntimeError("I am an error")
     456  except RuntimeError as e:
     457      id(e)
     458  ''')
     459          self.assertEqual(gdb_repr,
     460                           "RuntimeError('I am an error',)")
     461  
     462  
     463          # Test division by zero:
     464          gdb_repr, gdb_output = self.get_gdb_repr('''
     465  try:
     466      a = 1 / 0
     467  except ZeroDivisionError as e:
     468      id(e)
     469  ''')
     470          self.assertEqual(gdb_repr,
     471                           "ZeroDivisionError('division by zero',)")
     472  
     473      def test_modern_class(self):
     474          'Verify the pretty-printing of new-style class instances'
     475          gdb_repr, gdb_output = self.get_gdb_repr('''
     476  class Foo:
     477      pass
     478  foo = Foo()
     479  foo.an_int = 42
     480  id(foo)''')
     481          m = re.match(r'<Foo\(an_int=42\) at remote 0x-?[0-9a-f]+>', gdb_repr)
     482          self.assertTrue(m,
     483                          msg='Unexpected new-style class rendering %r' % gdb_repr)
     484  
     485      def test_subclassing_list(self):
     486          'Verify the pretty-printing of an instance of a list subclass'
     487          gdb_repr, gdb_output = self.get_gdb_repr('''
     488  class Foo(list):
     489      pass
     490  foo = Foo()
     491  foo += [1, 2, 3]
     492  foo.an_int = 42
     493  id(foo)''')
     494          m = re.match(r'<Foo\(an_int=42\) at remote 0x-?[0-9a-f]+>', gdb_repr)
     495  
     496          self.assertTrue(m,
     497                          msg='Unexpected new-style class rendering %r' % gdb_repr)
     498  
     499      def test_subclassing_tuple(self):
     500          'Verify the pretty-printing of an instance of a tuple subclass'
     501          # This should exercise the negative tp_dictoffset code in the
     502          # new-style class support
     503          gdb_repr, gdb_output = self.get_gdb_repr('''
     504  class Foo(tuple):
     505      pass
     506  foo = Foo((1, 2, 3))
     507  foo.an_int = 42
     508  id(foo)''')
     509          m = re.match(r'<Foo\(an_int=42\) at remote 0x-?[0-9a-f]+>', gdb_repr)
     510  
     511          self.assertTrue(m,
     512                          msg='Unexpected new-style class rendering %r' % gdb_repr)
     513  
     514      def assertSane(self, source, corruption, exprepr=None):
     515          '''Run Python under gdb, corrupting variables in the inferior process
     516          immediately before taking a backtrace.
     517  
     518          Verify that the variable's representation is the expected failsafe
     519          representation'''
     520          if corruption:
     521              cmds_after_breakpoint=[corruption, 'backtrace']
     522          else:
     523              cmds_after_breakpoint=['backtrace']
     524  
     525          gdb_repr, gdb_output = \
     526              self.get_gdb_repr(source,
     527                                cmds_after_breakpoint=cmds_after_breakpoint)
     528          if exprepr:
     529              if gdb_repr == exprepr:
     530                  # gdb managed to print the value in spite of the corruption;
     531                  # this is good (see http://bugs.python.org/issue8330)
     532                  return
     533  
     534          # Match anything for the type name; 0xDEADBEEF could point to
     535          # something arbitrary (see  http://bugs.python.org/issue8330)
     536          pattern = '<.* at remote 0x-?[0-9a-f]+>'
     537  
     538          m = re.match(pattern, gdb_repr)
     539          if not m:
     540              self.fail('Unexpected gdb representation: %r\n%s' % \
     541                            (gdb_repr, gdb_output))
     542  
     543      def test_NULL_ptr(self):
     544          'Ensure that a NULL PyObject* is handled gracefully'
     545          gdb_repr, gdb_output = (
     546              self.get_gdb_repr('id(42)',
     547                                cmds_after_breakpoint=['set variable v=0',
     548                                                       'backtrace'])
     549              )
     550  
     551          self.assertEqual(gdb_repr, '0x0')
     552  
     553      def test_NULL_ob_type(self):
     554          'Ensure that a PyObject* with NULL ob_type is handled gracefully'
     555          self.assertSane('id(42)',
     556                          'set v->ob_type=0')
     557  
     558      def test_corrupt_ob_type(self):
     559          'Ensure that a PyObject* with a corrupt ob_type is handled gracefully'
     560          self.assertSane('id(42)',
     561                          'set v->ob_type=0xDEADBEEF',
     562                          exprepr='42')
     563  
     564      def test_corrupt_tp_flags(self):
     565          'Ensure that a PyObject* with a type with corrupt tp_flags is handled'
     566          self.assertSane('id(42)',
     567                          'set v->ob_type->tp_flags=0x0',
     568                          exprepr='42')
     569  
     570      def test_corrupt_tp_name(self):
     571          'Ensure that a PyObject* with a type with corrupt tp_name is handled'
     572          self.assertSane('id(42)',
     573                          'set v->ob_type->tp_name=0xDEADBEEF',
     574                          exprepr='42')
     575  
     576      def test_builtins_help(self):
     577          'Ensure that the new-style class _Helper in site.py can be handled'
     578  
     579          if sys.flags.no_site:
     580              self.skipTest("need site module, but -S option was used")
     581  
     582          # (this was the issue causing tracebacks in
     583          #  http://bugs.python.org/issue8032#msg100537 )
     584          gdb_repr, gdb_output = self.get_gdb_repr('id(__builtins__.help)', import_site=True)
     585  
     586          m = re.match(r'<_Helper\(\) at remote 0x-?[0-9a-f]+>', gdb_repr)
     587          self.assertTrue(m,
     588                          msg='Unexpected rendering %r' % gdb_repr)
     589  
     590      def test_selfreferential_list(self):
     591          '''Ensure that a reference loop involving a list doesn't lead proxyval
     592          into an infinite loop:'''
     593          gdb_repr, gdb_output = \
     594              self.get_gdb_repr("a = [3, 4, 5] ; a.append(a) ; id(a)")
     595          self.assertEqual(gdb_repr, '[3, 4, 5, [...]]')
     596  
     597          gdb_repr, gdb_output = \
     598              self.get_gdb_repr("a = [3, 4, 5] ; b = [a] ; a.append(b) ; id(a)")
     599          self.assertEqual(gdb_repr, '[3, 4, 5, [[...]]]')
     600  
     601      def test_selfreferential_dict(self):
     602          '''Ensure that a reference loop involving a dict doesn't lead proxyval
     603          into an infinite loop:'''
     604          gdb_repr, gdb_output = \
     605              self.get_gdb_repr("a = {} ; b = {'bar':a} ; a['foo'] = b ; id(a)")
     606  
     607          self.assertEqual(gdb_repr, "{'foo': {'bar': {...}}}")
     608  
     609      def test_selfreferential_old_style_instance(self):
     610          gdb_repr, gdb_output = \
     611              self.get_gdb_repr('''
     612  class Foo:
     613      pass
     614  foo = Foo()
     615  foo.an_attr = foo
     616  id(foo)''')
     617          self.assertTrue(re.match(r'<Foo\(an_attr=<\.\.\.>\) at remote 0x-?[0-9a-f]+>',
     618                                   gdb_repr),
     619                          'Unexpected gdb representation: %r\n%s' % \
     620                              (gdb_repr, gdb_output))
     621  
     622      def test_selfreferential_new_style_instance(self):
     623          gdb_repr, gdb_output = \
     624              self.get_gdb_repr('''
     625  class Foo(object):
     626      pass
     627  foo = Foo()
     628  foo.an_attr = foo
     629  id(foo)''')
     630          self.assertTrue(re.match(r'<Foo\(an_attr=<\.\.\.>\) at remote 0x-?[0-9a-f]+>',
     631                                   gdb_repr),
     632                          'Unexpected gdb representation: %r\n%s' % \
     633                              (gdb_repr, gdb_output))
     634  
     635          gdb_repr, gdb_output = \
     636              self.get_gdb_repr('''
     637  class Foo(object):
     638      pass
     639  a = Foo()
     640  b = Foo()
     641  a.an_attr = b
     642  b.an_attr = a
     643  id(a)''')
     644          self.assertTrue(re.match(r'<Foo\(an_attr=<Foo\(an_attr=<\.\.\.>\) at remote 0x-?[0-9a-f]+>\) at remote 0x-?[0-9a-f]+>',
     645                                   gdb_repr),
     646                          'Unexpected gdb representation: %r\n%s' % \
     647                              (gdb_repr, gdb_output))
     648  
     649      def test_truncation(self):
     650          'Verify that very long output is truncated'
     651          gdb_repr, gdb_output = self.get_gdb_repr('id(list(range(1000)))')
     652          self.assertEqual(gdb_repr,
     653                           "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, "
     654                           "14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, "
     655                           "27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, "
     656                           "40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, "
     657                           "53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, "
     658                           "66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, "
     659                           "79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, "
     660                           "92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, "
     661                           "104, 105, 106, 107, 108, 109, 110, 111, 112, 113, "
     662                           "114, 115, 116, 117, 118, 119, 120, 121, 122, 123, "
     663                           "124, 125, 126, 127, 128, 129, 130, 131, 132, 133, "
     664                           "134, 135, 136, 137, 138, 139, 140, 141, 142, 143, "
     665                           "144, 145, 146, 147, 148, 149, 150, 151, 152, 153, "
     666                           "154, 155, 156, 157, 158, 159, 160, 161, 162, 163, "
     667                           "164, 165, 166, 167, 168, 169, 170, 171, 172, 173, "
     668                           "174, 175, 176, 177, 178, 179, 180, 181, 182, 183, "
     669                           "184, 185, 186, 187, 188, 189, 190, 191, 192, 193, "
     670                           "194, 195, 196, 197, 198, 199, 200, 201, 202, 203, "
     671                           "204, 205, 206, 207, 208, 209, 210, 211, 212, 213, "
     672                           "214, 215, 216, 217, 218, 219, 220, 221, 222, 223, "
     673                           "224, 225, 226...(truncated)")
     674          self.assertEqual(len(gdb_repr),
     675                           1024 + len('...(truncated)'))
     676  
     677      def test_builtin_method(self):
     678          gdb_repr, gdb_output = self.get_gdb_repr('import sys; id(sys.stdout.readlines)')
     679          self.assertTrue(re.match(r'<built-in method readlines of _io.TextIOWrapper object at remote 0x-?[0-9a-f]+>',
     680                                   gdb_repr),
     681                          'Unexpected gdb representation: %r\n%s' % \
     682                              (gdb_repr, gdb_output))
     683  
     684      def test_frames(self):
     685          gdb_output = self.get_stack_trace('''
     686  import sys
     687  def foo(a, b, c):
     688      return sys._getframe(0)
     689  
     690  f = foo(3, 4, 5)
     691  id(f)''',
     692                                            breakpoint='builtin_id',
     693                                            cmds_after_breakpoint=['print (PyFrameObject*)v']
     694                                            )
     695          self.assertTrue(re.match(r'.*\s+\$1 =\s+Frame 0x-?[0-9a-f]+, for file <string>, line 4, in foo \(a=3.*',
     696                                   gdb_output,
     697                                   re.DOTALL),
     698                          'Unexpected gdb representation: %r\n%s' % (gdb_output, gdb_output))
     699  
     700  @unittest.skipIf(python_is_optimized(),
     701                   "Python was compiled with optimizations")
     702  class ESC[4;38;5;81mPyListTests(ESC[4;38;5;149mDebuggerTests):
     703      def assertListing(self, expected, actual):
     704          self.assertEndsWith(actual, expected)
     705  
     706      def test_basic_command(self):
     707          'Verify that the "py-list" command works'
     708          bt = self.get_stack_trace(script=self.get_sample_script(),
     709                                    cmds_after_breakpoint=['py-list'])
     710  
     711          self.assertListing('   5    \n'
     712                             '   6    def bar(a, b, c):\n'
     713                             '   7        baz(a, b, c)\n'
     714                             '   8    \n'
     715                             '   9    def baz(*args):\n'
     716                             ' >10        id(42)\n'
     717                             '  11    \n'
     718                             '  12    foo(1, 2, 3)\n',
     719                             bt)
     720  
     721      def test_one_abs_arg(self):
     722          'Verify the "py-list" command with one absolute argument'
     723          bt = self.get_stack_trace(script=self.get_sample_script(),
     724                                    cmds_after_breakpoint=['py-list 9'])
     725  
     726          self.assertListing('   9    def baz(*args):\n'
     727                             ' >10        id(42)\n'
     728                             '  11    \n'
     729                             '  12    foo(1, 2, 3)\n',
     730                             bt)
     731  
     732      def test_two_abs_args(self):
     733          'Verify the "py-list" command with two absolute arguments'
     734          bt = self.get_stack_trace(script=self.get_sample_script(),
     735                                    cmds_after_breakpoint=['py-list 1,3'])
     736  
     737          self.assertListing('   1    # Sample script for use by test_gdb.py\n'
     738                             '   2    \n'
     739                             '   3    def foo(a, b, c):\n',
     740                             bt)
     741  
     742  SAMPLE_WITH_C_CALL = """
     743  
     744  from _testcapi import pyobject_fastcall
     745  
     746  def foo(a, b, c):
     747      bar(a, b, c)
     748  
     749  def bar(a, b, c):
     750      pyobject_fastcall(baz, (a, b, c))
     751  
     752  def baz(*args):
     753      id(42)
     754  
     755  foo(1, 2, 3)
     756  
     757  """
     758  
     759  
     760  class ESC[4;38;5;81mStackNavigationTests(ESC[4;38;5;149mDebuggerTests):
     761      @unittest.skipUnless(HAS_PYUP_PYDOWN, "test requires py-up/py-down commands")
     762      @unittest.skipIf(python_is_optimized(),
     763                       "Python was compiled with optimizations")
     764      def test_pyup_command(self):
     765          'Verify that the "py-up" command works'
     766          bt = self.get_stack_trace(source=SAMPLE_WITH_C_CALL,
     767                                    cmds_after_breakpoint=['py-up', 'py-up'])
     768          self.assertMultilineMatches(bt,
     769                                      r'''^.*
     770  #[0-9]+ Frame 0x-?[0-9a-f]+, for file <string>, line 12, in baz \(args=\(1, 2, 3\)\)
     771  #[0-9]+ <built-in method pyobject_fastcall of module object at remote 0x[0-9a-f]+>
     772  $''')
     773  
     774      @unittest.skipUnless(HAS_PYUP_PYDOWN, "test requires py-up/py-down commands")
     775      def test_down_at_bottom(self):
     776          'Verify handling of "py-down" at the bottom of the stack'
     777          bt = self.get_stack_trace(script=self.get_sample_script(),
     778                                    cmds_after_breakpoint=['py-down'])
     779          self.assertEndsWith(bt,
     780                              'Unable to find a newer python frame\n')
     781  
     782      @unittest.skipUnless(HAS_PYUP_PYDOWN, "test requires py-up/py-down commands")
     783      def test_up_at_top(self):
     784          'Verify handling of "py-up" at the top of the stack'
     785          bt = self.get_stack_trace(script=self.get_sample_script(),
     786                                    cmds_after_breakpoint=['py-up'] * 5)
     787          self.assertEndsWith(bt,
     788                              'Unable to find an older python frame\n')
     789  
     790      @unittest.skipUnless(HAS_PYUP_PYDOWN, "test requires py-up/py-down commands")
     791      @unittest.skipIf(python_is_optimized(),
     792                       "Python was compiled with optimizations")
     793      def test_up_then_down(self):
     794          'Verify "py-up" followed by "py-down"'
     795          bt = self.get_stack_trace(source=SAMPLE_WITH_C_CALL,
     796                                    cmds_after_breakpoint=['py-up', 'py-up', 'py-down'])
     797          self.assertMultilineMatches(bt,
     798                                      r'''^.*
     799  #[0-9]+ Frame 0x-?[0-9a-f]+, for file <string>, line 12, in baz \(args=\(1, 2, 3\)\)
     800  #[0-9]+ <built-in method pyobject_fastcall of module object at remote 0x[0-9a-f]+>
     801  #[0-9]+ Frame 0x-?[0-9a-f]+, for file <string>, line 12, in baz \(args=\(1, 2, 3\)\)
     802  $''')
     803  
     804  class ESC[4;38;5;81mPyBtTests(ESC[4;38;5;149mDebuggerTests):
     805      @unittest.skipIf(python_is_optimized(),
     806                       "Python was compiled with optimizations")
     807      def test_bt(self):
     808          'Verify that the "py-bt" command works'
     809          bt = self.get_stack_trace(script=self.get_sample_script(),
     810                                    cmds_after_breakpoint=['py-bt'])
     811          self.assertMultilineMatches(bt,
     812                                      r'''^.*
     813  Traceback \(most recent call first\):
     814    <built-in method id of module object .*>
     815    File ".*gdb_sample.py", line 10, in baz
     816      id\(42\)
     817    File ".*gdb_sample.py", line 7, in bar
     818      baz\(a, b, c\)
     819    File ".*gdb_sample.py", line 4, in foo
     820      bar\(a=a, b=b, c=c\)
     821    File ".*gdb_sample.py", line 12, in <module>
     822      foo\(1, 2, 3\)
     823  ''')
     824  
     825      @unittest.skipIf(python_is_optimized(),
     826                       "Python was compiled with optimizations")
     827      def test_bt_full(self):
     828          'Verify that the "py-bt-full" command works'
     829          bt = self.get_stack_trace(script=self.get_sample_script(),
     830                                    cmds_after_breakpoint=['py-bt-full'])
     831          self.assertMultilineMatches(bt,
     832                                      r'''^.*
     833  #[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 7, in bar \(a=1, b=2, c=3\)
     834      baz\(a, b, c\)
     835  #[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 4, in foo \(a=1, b=2, c=3\)
     836      bar\(a=a, b=b, c=c\)
     837  #[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 12, in <module> \(\)
     838      foo\(1, 2, 3\)
     839  ''')
     840  
     841      @unittest.skipIf(python_is_optimized(),
     842                       "Python was compiled with optimizations")
     843      @support.requires_resource('cpu')
     844      def test_threads(self):
     845          'Verify that "py-bt" indicates threads that are waiting for the GIL'
     846          cmd = '''
     847  from threading import Thread
     848  
     849  class TestThread(Thread):
     850      # These threads would run forever, but we'll interrupt things with the
     851      # debugger
     852      def run(self):
     853          i = 0
     854          while 1:
     855               i += 1
     856  
     857  t = {}
     858  for i in range(4):
     859     t[i] = TestThread()
     860     t[i].start()
     861  
     862  # Trigger a breakpoint on the main thread
     863  id(42)
     864  
     865  '''
     866          # Verify with "py-bt":
     867          gdb_output = self.get_stack_trace(cmd,
     868                                            cmds_after_breakpoint=['thread apply all py-bt'])
     869          self.assertIn('Waiting for the GIL', gdb_output)
     870  
     871          # Verify with "py-bt-full":
     872          gdb_output = self.get_stack_trace(cmd,
     873                                            cmds_after_breakpoint=['thread apply all py-bt-full'])
     874          self.assertIn('Waiting for the GIL', gdb_output)
     875  
     876      @unittest.skipIf(python_is_optimized(),
     877                       "Python was compiled with optimizations")
     878      # Some older versions of gdb will fail with
     879      #  "Cannot find new threads: generic error"
     880      # unless we add LD_PRELOAD=PATH-TO-libpthread.so.1 as a workaround
     881      def test_gc(self):
     882          'Verify that "py-bt" indicates if a thread is garbage-collecting'
     883          cmd = ('from gc import collect\n'
     884                 'id(42)\n'
     885                 'def foo():\n'
     886                 '    collect()\n'
     887                 'def bar():\n'
     888                 '    foo()\n'
     889                 'bar()\n')
     890          # Verify with "py-bt":
     891          gdb_output = self.get_stack_trace(cmd,
     892                                            cmds_after_breakpoint=['break update_refs', 'continue', 'py-bt'],
     893                                            )
     894          self.assertIn('Garbage-collecting', gdb_output)
     895  
     896          # Verify with "py-bt-full":
     897          gdb_output = self.get_stack_trace(cmd,
     898                                            cmds_after_breakpoint=['break update_refs', 'continue', 'py-bt-full'],
     899                                            )
     900          self.assertIn('Garbage-collecting', gdb_output)
     901  
     902  
     903      @unittest.skipIf(python_is_optimized(),
     904                       "Python was compiled with optimizations")
     905      @support.requires_resource('cpu')
     906      # Some older versions of gdb will fail with
     907      #  "Cannot find new threads: generic error"
     908      # unless we add LD_PRELOAD=PATH-TO-libpthread.so.1 as a workaround
     909      #
     910      # gdb will also generate many erroneous errors such as:
     911      #     Function "meth_varargs" not defined.
     912      # This is because we are calling functions from an "external" module
     913      # (_testcapimodule) rather than compiled-in functions. It seems difficult
     914      # to suppress these. See also the comment in DebuggerTests.get_stack_trace
     915      def test_pycfunction(self):
     916          'Verify that "py-bt" displays invocations of PyCFunction instances'
     917          # bpo-46600: If the compiler inlines _null_to_none() in meth_varargs()
     918          # (ex: clang -Og), _null_to_none() is the frame #1. Otherwise,
     919          # meth_varargs() is the frame #1.
     920          expected_frame = r'#(1|2)'
     921          # Various optimizations multiply the code paths by which these are
     922          # called, so test a variety of calling conventions.
     923          for func_name, args in (
     924              ('meth_varargs', ''),
     925              ('meth_varargs_keywords', ''),
     926              ('meth_o', '[]'),
     927              ('meth_noargs', ''),
     928              ('meth_fastcall', ''),
     929              ('meth_fastcall_keywords', ''),
     930          ):
     931              for obj in (
     932                  '_testcapi',
     933                  '_testcapi.MethClass',
     934                  '_testcapi.MethClass()',
     935                  '_testcapi.MethStatic()',
     936  
     937                  # XXX: bound methods don't yet give nice tracebacks
     938                  # '_testcapi.MethInstance()',
     939              ):
     940                  with self.subTest(f'{obj}.{func_name}'):
     941                      cmd = textwrap.dedent(f'''
     942                          import _testcapi
     943                          def foo():
     944                              {obj}.{func_name}({args})
     945                          def bar():
     946                              foo()
     947                          bar()
     948                      ''')
     949                      # Verify with "py-bt":
     950                      gdb_output = self.get_stack_trace(
     951                          cmd,
     952                          breakpoint=func_name,
     953                          cmds_after_breakpoint=['bt', 'py-bt'],
     954                          # bpo-45207: Ignore 'Function "meth_varargs" not
     955                          # defined.' message in stderr.
     956                          ignore_stderr=True,
     957                      )
     958                      self.assertIn(f'<built-in method {func_name}', gdb_output)
     959  
     960                      # Verify with "py-bt-full":
     961                      gdb_output = self.get_stack_trace(
     962                          cmd,
     963                          breakpoint=func_name,
     964                          cmds_after_breakpoint=['py-bt-full'],
     965                          # bpo-45207: Ignore 'Function "meth_varargs" not
     966                          # defined.' message in stderr.
     967                          ignore_stderr=True,
     968                      )
     969                      regex = expected_frame
     970                      regex += re.escape(f' <built-in method {func_name}')
     971                      self.assertRegex(gdb_output, regex)
     972  
     973      @unittest.skipIf(python_is_optimized(),
     974                       "Python was compiled with optimizations")
     975      def test_wrapper_call(self):
     976          cmd = textwrap.dedent('''
     977              class MyList(list):
     978                  def __init__(self):
     979                      super(*[]).__init__()   # wrapper_call()
     980  
     981              id("first break point")
     982              l = MyList()
     983          ''')
     984          cmds_after_breakpoint = ['break wrapper_call', 'continue']
     985          if CET_PROTECTION:
     986              # bpo-32962: same case as in get_stack_trace():
     987              # we need an additional 'next' command in order to read
     988              # arguments of the innermost function of the call stack.
     989              cmds_after_breakpoint.append('next')
     990          cmds_after_breakpoint.append('py-bt')
     991  
     992          # Verify with "py-bt":
     993          gdb_output = self.get_stack_trace(cmd,
     994                                            cmds_after_breakpoint=cmds_after_breakpoint)
     995          self.assertRegex(gdb_output,
     996                           r"<method-wrapper u?'__init__' of MyList object at ")
     997  
     998  class ESC[4;38;5;81mPyPrintTests(ESC[4;38;5;149mDebuggerTests):
     999      @unittest.skipIf(python_is_optimized(),
    1000                       "Python was compiled with optimizations")
    1001      def test_basic_command(self):
    1002          'Verify that the "py-print" command works'
    1003          bt = self.get_stack_trace(source=SAMPLE_WITH_C_CALL,
    1004                                    cmds_after_breakpoint=['py-up', 'py-print args'])
    1005          self.assertMultilineMatches(bt,
    1006                                      r".*\nlocal 'args' = \(1, 2, 3\)\n.*")
    1007  
    1008      @unittest.skipIf(python_is_optimized(),
    1009                       "Python was compiled with optimizations")
    1010      @unittest.skipUnless(HAS_PYUP_PYDOWN, "test requires py-up/py-down commands")
    1011      def test_print_after_up(self):
    1012          bt = self.get_stack_trace(source=SAMPLE_WITH_C_CALL,
    1013                                    cmds_after_breakpoint=['py-up', 'py-up', 'py-print c', 'py-print b', 'py-print a'])
    1014          self.assertMultilineMatches(bt,
    1015                                      r".*\nlocal 'c' = 3\nlocal 'b' = 2\nlocal 'a' = 1\n.*")
    1016  
    1017      @unittest.skipIf(python_is_optimized(),
    1018                       "Python was compiled with optimizations")
    1019      def test_printing_global(self):
    1020          bt = self.get_stack_trace(script=self.get_sample_script(),
    1021                                    cmds_after_breakpoint=['py-up', 'py-print __name__'])
    1022          self.assertMultilineMatches(bt,
    1023                                      r".*\nglobal '__name__' = '__main__'\n.*")
    1024  
    1025      @unittest.skipIf(python_is_optimized(),
    1026                       "Python was compiled with optimizations")
    1027      def test_printing_builtin(self):
    1028          bt = self.get_stack_trace(script=self.get_sample_script(),
    1029                                    cmds_after_breakpoint=['py-up', 'py-print len'])
    1030          self.assertMultilineMatches(bt,
    1031                                      r".*\nbuiltin 'len' = <built-in method len of module object at remote 0x-?[0-9a-f]+>\n.*")
    1032  
    1033  class ESC[4;38;5;81mPyLocalsTests(ESC[4;38;5;149mDebuggerTests):
    1034      @unittest.skipIf(python_is_optimized(),
    1035                       "Python was compiled with optimizations")
    1036      def test_basic_command(self):
    1037          bt = self.get_stack_trace(script=self.get_sample_script(),
    1038                                    cmds_after_breakpoint=['py-up', 'py-locals'])
    1039          self.assertMultilineMatches(bt,
    1040                                      r".*\nargs = \(1, 2, 3\)\n.*")
    1041  
    1042      @unittest.skipUnless(HAS_PYUP_PYDOWN, "test requires py-up/py-down commands")
    1043      @unittest.skipIf(python_is_optimized(),
    1044                       "Python was compiled with optimizations")
    1045      def test_locals_after_up(self):
    1046          bt = self.get_stack_trace(script=self.get_sample_script(),
    1047                                    cmds_after_breakpoint=['py-up', 'py-up', 'py-locals'])
    1048          self.assertMultilineMatches(bt,
    1049                                      r'''^.*
    1050  Locals for foo
    1051  a = 1
    1052  b = 2
    1053  c = 3
    1054  Locals for <module>
    1055  .*$''')
    1056  
    1057  
    1058  def setUpModule():
    1059      if support.verbose:
    1060          print("GDB version %s.%s:" % (gdb_major_version, gdb_minor_version))
    1061          for line in gdb_version.splitlines():
    1062              print(" " * 4 + line)
    1063  
    1064  
    1065  if __name__ == "__main__":
    1066      unittest.main()