(root)/
glibc-2.38/
sysdeps/
unix/
sysv/
linux/
glibcsyscalls.py
       1  #!/usr/bin/python3
       2  # Helpers for glibc system call list processing.
       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  # <http://www.gnu.org/licenses/>.
      19  
      20  import os
      21  import re
      22  
      23  if __name__ != '__main__':
      24      # When called as a main program, this is not needed.
      25      import glibcextract
      26  
      27  def extract_system_call_name(macro):
      28      """Convert the macro name (with __NR_) to a system call name."""
      29      prefix = '__NR_'
      30      if macro.startswith(prefix):
      31          return macro[len(prefix):]
      32      else:
      33          raise ValueError('invalid system call name: {!r}'.format(macro))
      34  
      35  # Matches macros for system call names.
      36  RE_SYSCALL = re.compile('__NR_.*')
      37  
      38  # Some __NR_ constants are not real
      39  RE_PSEUDO_SYSCALL = re.compile(r"""__NR_(
      40      # Reserved system call.
      41      (unused|reserved)[0-9]+
      42  
      43      # Pseudo-system call which describes a range.
      44      |(syscalls|arch_specific_syscall|(OABI_)?SYSCALL_BASE|SYSCALL_MASK)
      45      |(|64_|[NO]32_)Linux(_syscalls)?
      46     )""", re.X)
      47  
      48  def kernel_constants(cc):
      49      """Return a dictionary with the kernel-defined system call numbers.
      50  
      51      This comes from <asm/unistd.h>.
      52  
      53      """
      54      return {extract_system_call_name(name) : int(value)
      55              for name, value in glibcextract.compute_macro_consts(
      56                      '#include <asm/unistd.h>\n'
      57                      # Regularize the kernel definitions if necessary.
      58                      '#include <fixup-asm-unistd.h>',
      59                      cc, macro_re=RE_SYSCALL, exclude_re=RE_PSEUDO_SYSCALL)
      60              .items()}
      61  
      62  class ESC[4;38;5;81mSyscallNamesList:
      63      """The list of known system call names.
      64  
      65      glibc keeps a list of system call names.  The <sys/syscall.h>
      66      header needs to provide a SYS_ name for each __NR_ macro,
      67      and the generated <bits/syscall.h> header uses an
      68      architecture-independent list, so that there is a chance that
      69      system calls arriving late on certain architectures will automatically
      70      get the expected SYS_ macro.
      71  
      72      syscalls: list of strings with system call names
      73      kernel_version: tuple of integers; the kernel version given in the file
      74  
      75      """
      76      def __init__(self, lines):
      77          self.syscalls = []
      78          old_name = None
      79          self.kernel_version = None
      80          self.__lines = tuple(lines)
      81          for line in self.__lines:
      82              line = line.strip()
      83              if (not line) or line[0] == '#':
      84                  continue
      85              comps = line.split()
      86              if len(comps) == 1:
      87                  self.syscalls.append(comps[0])
      88                  if old_name is not None:
      89                      if comps[0] < old_name:
      90                          raise ValueError(
      91                              'name list is not sorted: {!r} < {!r}'.format(
      92                                  comps[0], old_name))
      93                  old_name = comps[0]
      94                  continue
      95              if len(comps) == 2 and comps[0] == "kernel":
      96                  if self.kernel_version is not None:
      97                      raise ValueError(
      98                          "multiple kernel versions: {!r} and !{r}".format(
      99                              kernel_version, comps[1]))
     100                  self.kernel_version = tuple(map(int, comps[1].split(".")))
     101                  continue
     102              raise ValueError("invalid line: !r".format(line))
     103          if self.kernel_version is None:
     104              raise ValueError("missing kernel version")
     105  
     106      def merge(self, names):
     107          """Merge sequence NAMES and return the lines of the new file."""
     108          names = list(set(names) - set(self.syscalls))
     109          names.sort()
     110          names.reverse()
     111          result = []
     112          def emit_name():
     113              result.append(names[-1] + "\n")
     114              del names[-1]
     115  
     116          for line in self.__lines:
     117              comps = line.strip().split()
     118              if len(comps) == 1 and not comps[0].startswith("#"):
     119                  # File has a system call at this position.  Insert all
     120                  # the names that come before the name in the file
     121                  # lexicographically.
     122                  while names and names[-1] < comps[0]:
     123                      emit_name()
     124              result.append(line)
     125          while names:
     126              emit_name()
     127  
     128          return result
     129  
     130  def load_arch_syscall_header(path):
     131      """"Load the system call header form the file PATH.
     132  
     133      The file must consist of lines of this form:
     134  
     135      #define __NR_exit 1
     136  
     137      The file is parsed verbatim, without running it through a C
     138      preprocessor or parser.  The intent is that the file can be
     139      readily processed by tools.
     140  
     141      """
     142      with open(path) as inp:
     143          result = {}
     144          old_name = None
     145          for line in inp:
     146              line = line.strip()
     147  
     148              # Ignore the initial comment line.
     149              if line.startswith("/*") and line.endswith("*/"):
     150                  continue
     151  
     152              define, name, number = line.split(' ', 2)
     153              if define != '#define':
     154                  raise ValueError("invalid syscall header line: {!r}".format(
     155                      line))
     156              result[extract_system_call_name(name)] = int(number)
     157  
     158              # Check list order.
     159              if old_name is not None:
     160                  if name < old_name:
     161                      raise ValueError(
     162                          'system call list is not sorted: {!r} < {!r}'.format(
     163                              name, old_name))
     164              old_name = name
     165          return result
     166  
     167  def linux_kernel_version(cc):
     168      """Return the (major, minor) version of the Linux kernel headers."""
     169      sym_data = ['#include <linux/version.h>', 'START',
     170                  ('LINUX_VERSION_CODE', 'LINUX_VERSION_CODE')]
     171      val = glibcextract.compute_c_consts(sym_data, cc)['LINUX_VERSION_CODE']
     172      val = int(val)
     173      return ((val & 0xff0000) >> 16, (val & 0xff00) >> 8)
     174  
     175  class ESC[4;38;5;81mArchSyscall:
     176      """Canonical name and location of a syscall header."""
     177  
     178      def __init__(self, name, path):
     179          self.name = name
     180          self.path = path
     181  
     182      def __repr__(self):
     183          return 'ArchSyscall(name={!r}, patch={!r})'.format(
     184              self.name, self.path)
     185  
     186  def list_arch_syscall_headers(topdir):
     187      """A generator which returns all the ArchSyscall objects in a tree."""
     188  
     189      sysdeps = os.path.join(topdir, 'sysdeps', 'unix', 'sysv', 'linux')
     190      for root, dirs, files in os.walk(sysdeps):
     191          if root != sysdeps:
     192              for filename in files:
     193                  if filename == 'arch-syscall.h':
     194                      yield ArchSyscall(
     195                          name=os.path.relpath(root, sysdeps),
     196                          path=os.path.join(root, filename))
     197  
     198  def __main():
     199      """Entry point when called as the main program."""
     200  
     201      import argparse
     202      import sys
     203  
     204      # Top-level directory of the source tree.
     205      topdir = os.path.realpath(os.path.join(
     206          os.path.dirname(os.path.realpath(__file__)), *('..',) * 4))
     207  
     208      def get_parser():
     209          parser = argparse.ArgumentParser(description=__doc__)
     210          subparsers = parser.add_subparsers(dest='command', required=True)
     211          subparsers.add_parser('list-headers',
     212              help='Print the absolute paths of all arch-syscall.h header files')
     213          subparser = subparsers.add_parser('query-syscall',
     214              help='Summarize the implementation status of system calls')
     215          subparser.add_argument('syscalls', help='Which syscalls to check',
     216                                 nargs='+')
     217          return parser
     218      parser = get_parser()
     219      args = parser.parse_args()
     220  
     221      if args.command == 'list-headers':
     222          for header in sorted([syscall.path for syscall
     223                                in list_arch_syscall_headers(topdir)]):
     224              print(header)
     225  
     226      elif args.command == 'query-syscall':
     227          # List of system call tables.
     228          tables = sorted(list_arch_syscall_headers(topdir),
     229                            key=lambda syscall: syscall.name)
     230          for table in tables:
     231              table.numbers = load_arch_syscall_header(table.path)
     232  
     233          for nr in args.syscalls:
     234              defined = [table.name for table in tables
     235                             if nr in table.numbers]
     236              undefined = [table.name for table in tables
     237                               if nr not in table.numbers]
     238              if not defined:
     239                  print('{}: not defined on any architecture'.format(nr))
     240              elif not undefined:
     241                  print('{}: defined on all architectures'.format(nr))
     242              else:
     243                  print('{}:'.format(nr))
     244                  print('  defined: {}'.format(' '.join(defined)))
     245                  print('  undefined: {}'.format(' '.join(undefined)))
     246  
     247      else:
     248          # Unrecognized command.
     249          usage(1)
     250  
     251  if __name__ == '__main__':
     252      __main()