(root)/
glibc-2.38/
scripts/
glibcextract.py
       1  #!/usr/bin/python3
       2  # Extract information from C headers.
       3  # Copyright (C) 2018-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  import collections
      21  import os.path
      22  import re
      23  import subprocess
      24  import tempfile
      25  
      26  
      27  def compute_c_consts(sym_data, cc):
      28      """Compute the values of some C constants.
      29  
      30      The first argument is a list whose elements are either strings
      31      (preprocessor directives, or the special string 'START' to
      32      indicate this function should insert its initial boilerplate text
      33      in the output there) or pairs of strings (a name and a C
      34      expression for the corresponding value).  Preprocessor directives
      35      in the middle of the list may be used to select which constants
      36      end up being evaluated using which expressions.
      37  
      38      """
      39      out_lines = []
      40      for arg in sym_data:
      41          if isinstance(arg, str):
      42              if arg == 'START':
      43                  out_lines.append('void\ndummy (void)\n{')
      44              else:
      45                  out_lines.append(arg)
      46              continue
      47          name = arg[0]
      48          value = arg[1]
      49          out_lines.append('asm ("/* @@@name@@@%s@@@value@@@%%0@@@end@@@ */" '
      50                           ': : \"i\" ((long int) (%s)));'
      51                           % (name, value))
      52      out_lines.append('}')
      53      out_lines.append('')
      54      out_text = '\n'.join(out_lines)
      55      with tempfile.TemporaryDirectory() as temp_dir:
      56          c_file_name = os.path.join(temp_dir, 'test.c')
      57          s_file_name = os.path.join(temp_dir, 'test.s')
      58          with open(c_file_name, 'w') as c_file:
      59              c_file.write(out_text)
      60          # Compilation has to be from stdin to avoid the temporary file
      61          # name being written into the generated dependencies.
      62          cmd = ('%s -S -o %s -x c - < %s' % (cc, s_file_name, c_file_name))
      63          subprocess.check_call(cmd, shell=True)
      64          consts = {}
      65          with open(s_file_name, 'r') as s_file:
      66              for line in s_file:
      67                  match = re.search('@@@name@@@([^@]*)'
      68                                    '@@@value@@@[^0-9Xxa-fA-F-]*'
      69                                    '([0-9Xxa-fA-F-]+).*@@@end@@@', line)
      70                  if match:
      71                      if (match.group(1) in consts
      72                          and match.group(2) != consts[match.group(1)]):
      73                          raise ValueError('duplicate constant %s'
      74                                           % match.group(1))
      75                      consts[match.group(1)] = match.group(2)
      76          return consts
      77  
      78  
      79  def list_macros(source_text, cc):
      80      """List the preprocessor macros defined by the given source code.
      81  
      82      The return value is a pair of dicts, the first one mapping macro
      83      names to their expansions and the second one mapping macro names
      84      to lists of their arguments, or to None for object-like macros.
      85  
      86      """
      87      with tempfile.TemporaryDirectory() as temp_dir:
      88          c_file_name = os.path.join(temp_dir, 'test.c')
      89          i_file_name = os.path.join(temp_dir, 'test.i')
      90          with open(c_file_name, 'w') as c_file:
      91              c_file.write(source_text)
      92          cmd = ('%s -E -dM -o %s %s' % (cc, i_file_name, c_file_name))
      93          subprocess.check_call(cmd, shell=True)
      94          macros_exp = {}
      95          macros_args = {}
      96          with open(i_file_name, 'r') as i_file:
      97              for line in i_file:
      98                  match = re.fullmatch('#define ([0-9A-Za-z_]+)(.*)\n', line)
      99                  if not match:
     100                      raise ValueError('bad -dM output line: %s' % line)
     101                  name = match.group(1)
     102                  value = match.group(2)
     103                  if value.startswith(' '):
     104                      value = value[1:]
     105                      args = None
     106                  elif value.startswith('('):
     107                      match = re.fullmatch(r'\((.*?)\) (.*)', value)
     108                      if not match:
     109                          raise ValueError('bad -dM output line: %s' % line)
     110                      args = match.group(1).split(',')
     111                      value = match.group(2)
     112                  else:
     113                      raise ValueError('bad -dM output line: %s' % line)
     114                  if name in macros_exp:
     115                      raise ValueError('duplicate macro: %s' % line)
     116                  macros_exp[name] = value
     117                  macros_args[name] = args
     118      return macros_exp, macros_args
     119  
     120  
     121  def compute_macro_consts(source_text, cc, macro_re, exclude_re=None):
     122      """Compute the integer constant values of macros defined by source_text.
     123  
     124      Macros must match the regular expression macro_re, and if
     125      exclude_re is defined they must not match exclude_re.  Values are
     126      computed with compute_c_consts.
     127  
     128      """
     129      macros_exp, macros_args = list_macros(source_text, cc)
     130      macros_set = {m for m in macros_exp
     131                    if (macros_args[m] is None
     132                        and re.fullmatch(macro_re, m)
     133                        and (exclude_re is None
     134                             or not re.fullmatch(exclude_re, m)))}
     135      sym_data = [source_text, 'START']
     136      sym_data.extend(sorted((m, m) for m in macros_set))
     137      return compute_c_consts(sym_data, cc)
     138  
     139  
     140  def compare_macro_consts(source_1, source_2, cc, macro_re, exclude_re=None,
     141                           allow_extra_1=False, allow_extra_2=False):
     142      """Compare the values of macros defined by two different sources.
     143  
     144      The sources would typically be includes of a glibc header and a
     145      kernel header.  If allow_extra_1, the first source may define
     146      extra macros (typically if the kernel headers are older than the
     147      version glibc has taken definitions from); if allow_extra_2, the
     148      second source may define extra macros (typically if the kernel
     149      headers are newer than the version glibc has taken definitions
     150      from).  Return 1 if there were any differences other than those
     151      allowed, 0 if the macro values were the same apart from any
     152      allowed differences.
     153  
     154      """
     155      macros_1 = compute_macro_consts(source_1, cc, macro_re, exclude_re)
     156      macros_2 = compute_macro_consts(source_2, cc, macro_re, exclude_re)
     157      if macros_1 == macros_2:
     158          return 0
     159      print('First source:\n%s\n' % source_1)
     160      print('Second source:\n%s\n' % source_2)
     161      ret = 0
     162      for name, value in sorted(macros_1.items()):
     163          if name not in macros_2:
     164              print('Only in first source: %s' % name)
     165              if not allow_extra_1:
     166                  ret = 1
     167          elif macros_1[name] != macros_2[name]:
     168              print('Different values for %s: %s != %s'
     169                    % (name, macros_1[name], macros_2[name]))
     170              ret = 1
     171      for name in sorted(macros_2.keys()):
     172          if name not in macros_1:
     173              print('Only in second source: %s' % name)
     174              if not allow_extra_2:
     175                  ret = 1
     176      return ret
     177  
     178  CompileResult = collections.namedtuple("CompileResult", "returncode output")
     179  
     180  def compile_c_snippet(snippet, cc, extra_cc_args=''):
     181      """Compile and return whether the SNIPPET can be build with CC along
     182         EXTRA_CC_ARGS compiler flags.  Return a CompileResult with RETURNCODE
     183         being 0 for success, or the failure value and the compiler output.
     184      """
     185      with tempfile.TemporaryDirectory() as temp_dir:
     186          c_file_name = os.path.join(temp_dir, 'test.c')
     187          obj_file_name = os.path.join(temp_dir, 'test.o')
     188          with open(c_file_name, 'w') as c_file:
     189              c_file.write(snippet + '\n')
     190          cmd = cc.split() + extra_cc_args.split() + ['-c', '-o', obj_file_name,
     191                  c_file_name]
     192          r = subprocess.run(cmd, check=False, stdout=subprocess.PIPE,
     193                  stderr=subprocess.STDOUT)
     194          return CompileResult(r.returncode, r.stdout)