1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170
|
#!/usr/bin/python3
# Helpers for glibc system call list processing.
# Copyright (C) 2018-2020 Free Software Foundation, Inc.
# This file is part of the GNU C Library.
#
# The GNU C Library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# The GNU C Library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with the GNU C Library; if not, see
# <http://www.gnu.org/licenses/>.
import re
import glibcextract
def extract_system_call_name(macro):
"""Convert the macro name (with __NR_) to a system call name."""
prefix = '__NR_'
if macro.startswith(prefix):
return macro[len(prefix):]
else:
raise ValueError('invalid system call name: {!r}'.format(macro))
# Matches macros for systme call names.
RE_SYSCALL = re.compile('__NR_.*')
# Some __NR_ constants are not real
RE_PSEUDO_SYSCALL = re.compile(r"""__NR_(
# Reserved system call.
(unused|reserved)[0-9]+
# Pseudo-system call which describes a range.
|(syscalls|arch_specific_syscall|(OABI_)?SYSCALL_BASE)
|(|64_|[NO]32_)Linux(_syscalls)?
)""", re.X)
def kernel_constants(cc):
"""Return a dictionary with the kernel-defined system call numbers.
This comes from <asm/unistd.h>.
"""
return {extract_system_call_name(name) : int(value)
for name, value in glibcextract.compute_macro_consts(
'#include <asm/unistd.h>\n'
# Regularlize the kernel definitions if necessary.
'#include <fixup-asm-unistd.h>',
cc, macro_re=RE_SYSCALL, exclude_re=RE_PSEUDO_SYSCALL)
.items()}
class SyscallNamesList:
"""The list of known system call names.
glibc keeps a list of system call names. The <sys/syscall.h>
header needs to provide a SYS_ name for each __NR_ macro,
and the generated <bits/syscall.h> header uses an
architecture-independent list, so that there is a chance that
system calls arriving late on certain architectures will automatically
get the expected SYS_ macro.
syscalls: list of strings with system call names
kernel_version: tuple of integers; the kernel version given in the file
"""
def __init__(self, lines):
self.syscalls = []
old_name = None
self.kernel_version = None
self.__lines = tuple(lines)
for line in self.__lines:
line = line.strip()
if (not line) or line[0] == '#':
continue
comps = line.split()
if len(comps) == 1:
self.syscalls.append(comps[0])
if old_name is not None:
if comps[0] < old_name:
raise ValueError(
'name list is not sorted: {!r} < {!r}'.format(
comps[0], old_name))
old_name = comps[0]
continue
if len(comps) == 2 and comps[0] == "kernel":
if self.kernel_version is not None:
raise ValueError(
"multiple kernel versions: {!r} and !{r}".format(
kernel_version, comps[1]))
self.kernel_version = tuple(map(int, comps[1].split(".")))
continue
raise ValueError("invalid line: !r".format(line))
if self.kernel_version is None:
raise ValueError("missing kernel version")
def merge(self, names):
"""Merge sequence NAMES and return the lines of the new file."""
names = list(set(names) - set(self.syscalls))
names.sort()
names.reverse()
result = []
def emit_name():
result.append(names[-1] + "\n")
del names[-1]
for line in self.__lines:
comps = line.strip().split()
if len(comps) == 1 and not comps[0].startswith("#"):
# File has a system call at this position. Insert all
# the names that come before the name in the file
# lexicographically.
while names and names[-1] < comps[0]:
emit_name()
result.append(line)
while names:
emit_name()
return result
def load_arch_syscall_header(path):
""""Load the system call header form the file PATH.
The file must consist of lines of this form:
#define __NR_exit 1
The file is parsed verbatim, without running it through a C
preprocessor or parser. The intent is that the file can be
readily processed by tools.
"""
with open(path) as inp:
result = {}
old_name = None
for line in inp:
line = line.strip()
# Ignore the initial comment line.
if line.startswith("/*") and line.endswith("*/"):
continue
define, name, number = line.split(' ', 2)
if define != '#define':
raise ValueError("invalid syscall header line: {!r}".format(
line))
result[extract_system_call_name(name)] = int(number)
# Check list order.
if old_name is not None:
if name < old_name:
raise ValueError(
'system call list is not sorted: {!r} < {!r}'.format(
name, old_name))
old_name = name
return result
def linux_kernel_version(cc):
"""Return the (major, minor) version of the Linux kernel headers."""
sym_data = ['#include <linux/version.h>', 'START',
('LINUX_VERSION_CODE', 'LINUX_VERSION_CODE')]
val = glibcextract.compute_c_consts(sym_data, cc)['LINUX_VERSION_CODE']
val = int(val)
return ((val & 0xff0000) >> 16, (val & 0xff00) >> 8)
|