(root)/
glibc-2.38/
scripts/
test_printers_common.py
       1  # Common functions and variables for testing the Python pretty printers.
       2  #
       3  # Copyright (C) 2016-2023 Free Software Foundation, Inc.
       4  # This file is part of the GNU C Library.
       5  #
       6  # The GNU C Library is free software; you can redistribute it and/or
       7  # modify it under the terms of the GNU Lesser General Public
       8  # License as published by the Free Software Foundation; either
       9  # version 2.1 of the License, or (at your option) any later version.
      10  #
      11  # The GNU C Library is distributed in the hope that it will be useful,
      12  # but WITHOUT ANY WARRANTY; without even the implied warranty of
      13  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
      14  # Lesser General Public License for more details.
      15  #
      16  # You should have received a copy of the GNU Lesser General Public
      17  # License along with the GNU C Library; if not, see
      18  # <https://www.gnu.org/licenses/>.
      19  
      20  """These tests require PExpect 4.0 or newer.
      21  
      22  Exported constants:
      23      PASS, FAIL, UNSUPPORTED (int): Test exit codes, as per evaluate-test.sh.
      24  """
      25  
      26  import os
      27  import re
      28  from test_printers_exceptions import *
      29  
      30  PASS = 0
      31  FAIL = 1
      32  UNSUPPORTED = 77
      33  
      34  gdb_bin = 'gdb'
      35  gdb_options = '-q -nx'
      36  gdb_invocation = '{0} {1}'.format(gdb_bin, gdb_options)
      37  pexpect_min_version = 4
      38  gdb_min_version = (7, 8)
      39  encoding = 'utf-8'
      40  
      41  try:
      42      import pexpect
      43  except ImportError:
      44      print('PExpect 4.0 or newer must be installed to test the pretty printers.')
      45      exit(UNSUPPORTED)
      46  
      47  pexpect_version = pexpect.__version__.split('.')[0]
      48  
      49  if int(pexpect_version) < pexpect_min_version:
      50      print('PExpect 4.0 or newer must be installed to test the pretty printers.')
      51      exit(UNSUPPORTED)
      52  
      53  if not pexpect.which(gdb_bin):
      54      print('gdb 7.8 or newer must be installed to test the pretty printers.')
      55      exit(UNSUPPORTED)
      56  
      57  timeout = 5
      58  TIMEOUTFACTOR = os.environ.get('TIMEOUTFACTOR')
      59  
      60  if TIMEOUTFACTOR:
      61      timeout = int(TIMEOUTFACTOR)
      62  
      63  # Otherwise GDB is run in interactive mode and readline may send escape
      64  # sequences confusing output for pexpect.
      65  os.environ["TERM"]="dumb"
      66  
      67  try:
      68      # Check the gdb version.
      69      version_cmd = '{0} --version'.format(gdb_invocation, timeout=timeout)
      70      gdb_version_out = pexpect.run(version_cmd, encoding=encoding)
      71  
      72      # The gdb version string is "GNU gdb <PKGVERSION><version>", where
      73      # PKGVERSION can be any text.  We assume that there'll always be a space
      74      # between PKGVERSION and the version number for the sake of the regexp.
      75      version_match = re.search(r'GNU gdb .* ([1-9][0-9]*)\.([0-9]+)',
      76                                gdb_version_out)
      77  
      78      if not version_match:
      79          print('The gdb version string (gdb -v) is incorrectly formatted.')
      80          exit(UNSUPPORTED)
      81  
      82      gdb_version = (int(version_match.group(1)), int(version_match.group(2)))
      83  
      84      if gdb_version < gdb_min_version:
      85          print('gdb 7.8 or newer must be installed to test the pretty printers.')
      86          exit(UNSUPPORTED)
      87  
      88      # Check if gdb supports Python.
      89      gdb_python_cmd = '{0} -ex "python import os" -batch'.format(gdb_invocation,
      90                                                                  timeout=timeout)
      91      gdb_python_error = pexpect.run(gdb_python_cmd, encoding=encoding)
      92  
      93      if gdb_python_error:
      94          print('gdb must have python support to test the pretty printers.')
      95          print('gdb output: {!r}'.format(gdb_python_error))
      96          exit(UNSUPPORTED)
      97  
      98      # If everything's ok, spawn the gdb process we'll use for testing.
      99      gdb = pexpect.spawn(gdb_invocation, echo=False, timeout=timeout,
     100                          encoding=encoding)
     101      gdb_prompt = u'\(gdb\)'
     102      gdb.expect(gdb_prompt)
     103  
     104  except pexpect.ExceptionPexpect as exception:
     105      print('Error: {0}'.format(exception))
     106      exit(FAIL)
     107  
     108  def test(command, pattern=None):
     109      """Sends 'command' to gdb and expects the given 'pattern'.
     110  
     111      If 'pattern' is None, simply consumes everything up to and including
     112      the gdb prompt.
     113  
     114      Args:
     115          command (string): The command we'll send to gdb.
     116          pattern (raw string): A pattern the gdb output should match.
     117  
     118      Returns:
     119          string: The string that matched 'pattern', or an empty string if
     120              'pattern' was None.
     121      """
     122  
     123      match = ''
     124  
     125      gdb.sendline(command)
     126  
     127      if pattern:
     128          # PExpect does a non-greedy match for '+' and '*'.  Since it can't look
     129          # ahead on the gdb output stream, if 'pattern' ends with a '+' or a '*'
     130          # we may end up matching only part of the required output.
     131          # To avoid this, we'll consume 'pattern' and anything that follows it
     132          # up to and including the gdb prompt, then extract 'pattern' later.
     133          index = gdb.expect([u'{0}.+{1}'.format(pattern, gdb_prompt),
     134                              pexpect.TIMEOUT])
     135  
     136          if index == 0:
     137              # gdb.after now contains the whole match.  Extract the text that
     138              # matches 'pattern'.
     139              match = re.match(pattern, gdb.after, re.DOTALL).group()
     140          elif index == 1:
     141              # We got a timeout exception.  Print information on what caused it
     142              # and bail out.
     143              error = ('Response does not match the expected pattern.\n'
     144                       'Command: {0}\n'
     145                       'Expected pattern: {1}\n'
     146                       'Response: {2}'.format(command, pattern, gdb.before))
     147  
     148              raise pexpect.TIMEOUT(error)
     149      else:
     150          # Consume just the the gdb prompt.
     151          gdb.expect(gdb_prompt)
     152  
     153      return match
     154  
     155  def init_test(test_bin, printer_files, printer_names):
     156      """Loads the test binary file and the required pretty printers to gdb.
     157  
     158      Args:
     159          test_bin (string): The name of the test binary file.
     160          pretty_printers (list of strings): A list with the names of the pretty
     161              printer files.
     162      """
     163  
     164      # Disable debuginfod to avoid GDB messages like:
     165      #
     166      # This GDB supports auto-downloading debuginfo from the following URLs:
     167      # https://debuginfod.fedoraproject.org/
     168      # Enable debuginfod for this session? (y or [n])
     169      #
     170      try:
     171          test('set debuginfod enabled off')
     172      except Exception:
     173          pass
     174  
     175      # Load all the pretty printer files.  We're assuming these are safe.
     176      for printer_file in printer_files:
     177          test('source {0}'.format(printer_file))
     178  
     179      # Disable all the pretty printers.
     180      test('disable pretty-printer', r'0 of [0-9]+ printers enabled')
     181  
     182      # Enable only the required printers.
     183      for printer in printer_names:
     184          test('enable pretty-printer {0}'.format(printer),
     185               r'[1-9][0-9]* of [1-9]+ printers enabled')
     186  
     187      # Finally, load the test binary.
     188      test('file {0}'.format(test_bin))
     189  
     190      # Disable lock elision.
     191      test('set environment GLIBC_TUNABLES glibc.elision.enable=0')
     192  
     193  def go_to_main():
     194      """Executes a gdb 'start' command, which takes us to main."""
     195  
     196      test('start', r'main')
     197  
     198  def get_line_number(file_name, string):
     199      """Returns the number of the line in which 'string' appears within a file.
     200  
     201      Args:
     202          file_name (string): The name of the file we'll search through.
     203          string (string): The string we'll look for.
     204  
     205      Returns:
     206          int: The number of the line in which 'string' appears, starting from 1.
     207      """
     208      number = -1
     209  
     210      with open(file_name) as src_file:
     211          for i, line in enumerate(src_file):
     212              if string in line:
     213                  number = i + 1
     214                  break
     215  
     216      if number == -1:
     217          raise NoLineError(file_name, string)
     218  
     219      return number
     220  
     221  def break_at(file_name, string, temporary=True, thread=None):
     222      """Places a breakpoint on the first line in 'file_name' containing 'string'.
     223  
     224      'string' is usually a comment like "Stop here".  Notice this may fail unless
     225      the comment is placed inline next to actual code, e.g.:
     226  
     227          ...
     228          /* Stop here */
     229          ...
     230  
     231      may fail, while:
     232  
     233          ...
     234          some_func(); /* Stop here */
     235          ...
     236  
     237      will succeed.
     238  
     239      If 'thread' isn't None, the breakpoint will be set for all the threads.
     240      Otherwise, it'll be set only for 'thread'.
     241  
     242      Args:
     243          file_name (string): The name of the file we'll place the breakpoint in.
     244          string (string): A string we'll look for inside the file.
     245              We'll place a breakpoint on the line which contains it.
     246          temporary (bool): Whether the breakpoint should be automatically deleted
     247              after we reach it.
     248          thread (int): The number of the thread we'll place the breakpoint for,
     249              as seen by gdb.  If specified, it should be greater than zero.
     250      """
     251  
     252      if not thread:
     253          thread_str = ''
     254      else:
     255          thread_str = 'thread {0}'.format(thread)
     256  
     257      if temporary:
     258          command = 'tbreak'
     259          break_type = 'Temporary breakpoint'
     260      else:
     261          command = 'break'
     262          break_type = 'Breakpoint'
     263  
     264      line_number = str(get_line_number(file_name, string))
     265  
     266      test('{0} {1}:{2} {3}'.format(command, file_name, line_number, thread_str),
     267           r'{0} [0-9]+ at 0x[a-f0-9]+: file {1}, line {2}\.'.format(break_type,
     268                                                                     file_name,
     269                                                                     line_number))
     270  
     271  def continue_cmd(thread=None):
     272      """Executes a gdb 'continue' command.
     273  
     274      If 'thread' isn't None, the command will be applied to all the threads.
     275      Otherwise, it'll be applied only to 'thread'.
     276  
     277      Args:
     278          thread (int): The number of the thread we'll apply the command to,
     279              as seen by gdb.  If specified, it should be greater than zero.
     280      """
     281  
     282      if not thread:
     283          command = 'continue'
     284      else:
     285          command = 'thread apply {0} continue'.format(thread)
     286  
     287      test(command)
     288  
     289  def next_cmd(count=1, thread=None):
     290      """Executes a gdb 'next' command.
     291  
     292      If 'thread' isn't None, the command will be applied to all the threads.
     293      Otherwise, it'll be applied only to 'thread'.
     294  
     295      Args:
     296          count (int): The 'count' argument of the 'next' command.
     297          thread (int): The number of the thread we'll apply the command to,
     298              as seen by gdb.  If specified, it should be greater than zero.
     299      """
     300  
     301      if not thread:
     302          command = 'next'
     303      else:
     304          command = 'thread apply {0} next'
     305  
     306      test('{0} {1}'.format(command, count))
     307  
     308  def select_thread(thread):
     309      """Selects the thread indicated by 'thread'.
     310  
     311      Args:
     312          thread (int): The number of the thread we'll switch to, as seen by gdb.
     313              This should be greater than zero.
     314      """
     315  
     316      if thread > 0:
     317          test('thread {0}'.format(thread))
     318  
     319  def get_current_thread_lwpid():
     320      """Gets the current thread's Lightweight Process ID.
     321  
     322      Returns:
     323          string: The current thread's LWP ID.
     324      """
     325  
     326      # It's easier to get the LWP ID through the Python API than the gdb CLI.
     327      command = 'python print(gdb.selected_thread().ptid[1])'
     328  
     329      return test(command, r'[0-9]+')
     330  
     331  def set_scheduler_locking(mode):
     332      """Executes the gdb 'set scheduler-locking' command.
     333  
     334      Args:
     335          mode (bool): Whether the scheduler locking mode should be 'on'.
     336      """
     337      modes = {
     338          True: 'on',
     339          False: 'off'
     340      }
     341  
     342      test('set scheduler-locking {0}'.format(modes[mode]))
     343  
     344  def test_printer(var, to_string, children=None, is_ptr=True):
     345      """ Tests the output of a pretty printer.
     346  
     347      For a variable called 'var', this tests whether its associated printer
     348      outputs the expected 'to_string' and children (if any).
     349  
     350      Args:
     351          var (string): The name of the variable we'll print.
     352          to_string (raw string): The expected output of the printer's 'to_string'
     353              method.
     354          children (map {raw string->raw string}): A map with the expected output
     355              of the printer's children' method.
     356          is_ptr (bool): Whether 'var' is a pointer, and thus should be
     357              dereferenced.
     358      """
     359  
     360      if is_ptr:
     361          var = '*{0}'.format(var)
     362  
     363      test('print {0}'.format(var), to_string)
     364  
     365      if children:
     366          for name, value in children.items():
     367              # Children are shown as 'name = value'.
     368              test('print {0}'.format(var), r'{0} = {1}'.format(name, value))
     369  
     370  def check_debug_symbol(symbol):
     371      """ Tests whether a given debugging symbol exists.
     372  
     373      If the symbol doesn't exist, raises a DebugError.
     374  
     375      Args:
     376          symbol (string): The symbol we're going to check for.
     377      """
     378  
     379      try:
     380          test('ptype {0}'.format(symbol), r'type = {0}'.format(symbol))
     381  
     382      except pexpect.TIMEOUT:
     383          # The symbol doesn't exist.
     384          raise DebugError(symbol)