• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/python
2#
3# Copyright (C) 2020 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9#      http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16#
17
18import json
19import os.path
20import sys
21
22
23# <num> <abi> <name> [<entry point> [<oabi compat entry point>]]
24def _parse_arm_syscalls(tbl_file):
25  res = {}
26
27  for line in tbl_file:
28    line = line.strip()
29    if line.startswith('#'):
30      continue
31
32    words = line.split()
33    id = words[0]
34    abi = words[1]
35    name = '__NR_' + words[2]
36
37    if abi == 'oabi':
38      continue
39    assert abi == 'common' or abi == 'eabi'
40
41    if len(words) < 4:
42      # TODO(b/232598137): ok? missing syscall should SIGILL, sys_ni_syscall returns ENOSYS!
43      entry = 'sys_ni_syscall'
44    else:
45      entry = words[3]
46      # TODO(b/232598137): ok? check if wrappers somehow change action!
47      if entry.endswith('_wrapper'):
48        entry = entry[:-8]
49
50    res[name] = {'id': id, 'entry': entry}
51
52  return res
53
54
55# <number> <abi> <name> <entry point> <compat entry point>
56def _parse_x86_syscalls(tbl_file):
57  res = {}
58
59  for line in tbl_file:
60    line = line.strip()
61    if line.startswith('#'):
62      continue
63
64    words = line.split()
65    id = words[0]
66    abi = words[1]
67    name = '__NR_' + words[2]
68
69    assert abi == 'i386'
70
71    if len(words) < 4:
72      # TODO(b/232598137): ok? missing syscall should SIGILL, sys_ni_syscall returns ENOSYS!
73      entry = 'sys_ni_syscall'
74    else:
75      entry = words[3]
76
77    res[name] = {'id': id, 'entry': entry}
78
79  return res
80
81
82# <number> <abi> <name> <entry point>[/<qualifier>]
83def _parse_x86_64_syscalls(tbl_file):
84  res = {}
85
86  for line in tbl_file:
87    line = line.strip()
88    if not line:
89      continue
90    if line.startswith('#'):
91      continue
92
93    words = line.split()
94    id = words[0]
95    abi = words[1]
96    name = '__NR_' + words[2]
97
98    if abi == 'x32':
99      continue
100    assert abi == 'common' or abi == '64'
101
102    if len(words) < 4:
103      # TODO(berberis): Is it ok? Missing syscall should SIGILL, sys_ni_syscall returns ENOSYS.
104      entry = 'sys_ni_syscall'
105    else:
106      entry = words[3]
107
108    res[name] = {'id': id, 'entry': entry}
109
110  return res
111
112
113# #define __NR_read 63
114# __SYSCALL(__NR_read, sys_read)
115def _parse_unistd_syscalls(header_file):
116  res = {}
117
118  defines = {}
119  syscalls = {}
120
121  prefix = ''
122  cond = []
123
124  for line in header_file:
125    line = line.strip()
126    if not line:
127      continue
128
129    # handle line concatenation
130    if line.endswith('\\'):
131      prefix += line[:-1]
132      continue
133    if prefix:
134      line = prefix + line
135      prefix = ''
136    # add new conditional
137    if line in [
138        '#ifndef __SYSCALL',
139        '#if __BITS_PER_LONG == 32 || defined(__SYSCALL_COMPAT)',
140        '#if defined(__SYSCALL_COMPAT) || __BITS_PER_LONG == 32',
141        '#ifdef __SYSCALL_COMPAT',
142        '#ifdef __ARCH_WANT_SYNC_FILE_RANGE2',
143        '#if __BITS_PER_LONG == 32',
144        '#ifdef __NR3264_stat',
145        ]:
146      cond.append(False)
147      continue
148    if line in [
149        '#if defined(__ARCH_WANT_TIME32_SYSCALLS) || __BITS_PER_LONG != 32',
150        '#ifdef __ARCH_WANT_RENAMEAT',
151        '#if defined(__ARCH_WANT_NEW_STAT) || defined(__ARCH_WANT_STAT64)',
152        '#ifdef __ARCH_WANT_SET_GET_RLIMIT',
153        '#ifndef __ARCH_NOMMU',
154        '#ifdef __ARCH_WANT_SYS_CLONE3',
155        '#if __BITS_PER_LONG == 64 && !defined(__SYSCALL_COMPAT)',
156        '#ifdef __ARCH_WANT_MEMFD_SECRET',
157        ]:
158      cond.append(True)
159      continue
160    if line.startswith('#if'):
161      assert False
162
163    # change current conditional
164    if line.startswith('#endif'):
165      cond.pop()
166      continue
167    if line.startswith('#else'):
168      cond.append(not cond.pop())
169      continue
170    if line.startswith('#elif'):
171      assert False
172
173    # check current conditional
174    if False in cond:
175      continue
176
177    # defines
178    if line.startswith('#define __NR'):
179      words = line.split()
180      defines[words[1]] = words[2]
181      continue
182
183    # syscall
184    if line.startswith('__SYSCALL('):
185      words = line.replace('(', ' ').replace(')', ' ').replace(',', ' ').split()
186      syscalls[words[1]] = words[2]
187      continue
188    if line.startswith('__SC_COMP('):
189      words = line.replace('(', ' ').replace(')', ' ').replace(',', ' ').split()
190      syscalls[words[1]] = words[2]
191      continue
192    if line.startswith('__SC_3264('):
193      words = line.replace('(', ' ').replace(')', ' ').replace(',', ' ').split()
194      syscalls[words[1]] = words[3]
195      continue
196    if line.startswith('__SC_COMP_3264('):
197      words = line.replace('(', ' ').replace(')', ' ').replace(',', ' ').split()
198      syscalls[words[1]] = words[3]
199      continue
200    if line.startswith('__S'):
201      assert False
202
203  for name, entry in syscalls.items():
204    id = defines[name]
205
206    # apply redefines
207    for name_to, name_from in defines.items():
208      if name == name_from:
209        name = name_to
210        break
211
212    assert name.startswith('__NR_')
213
214    res[name] = {'id': id, 'entry': entry}
215
216  return res
217
218
219# asmlinkage long sys_read(unsigned int fd, char __user *buf, size_t count);
220def _parse_protos(header_file):
221  res = {}
222
223  prefix = ''
224  proto = ''
225
226  for line in header_file:
227    line = line.strip()
228    if not line:
229      continue
230
231    # handle line concatenation (to skip protos within multiline #define)
232    if line.endswith('\\'):
233      prefix += line[:-1]
234      continue
235    if prefix:
236      line = prefix + line
237      prefix = ''
238
239    if line.startswith('asmlinkage long '):
240      assert not proto
241      proto = line
242    elif proto:
243      proto += ' '
244      proto += line
245    else:
246      continue
247
248    if ');' in proto:
249      # As arch might have specific calling conventions, so prototype might be not precise.
250      # We'll extract precise prototype from DWARF.
251      # Here, we just distinguish entries with and without parameters.
252      # unfortunately, few entries have multiple protos, selection configured by arch defines
253      # (why not providing distinct entries instead???)
254      entry = proto[16: proto.find('(')].strip()
255      if '(void);' not in proto:
256        res[entry] = True
257      else:
258        res.setdefault(entry, False)
259      proto = ''
260
261  return res
262
263
264def main(argv):
265  if len(argv) != 2:
266    print('Usage: %s <kernel-src-dir>' % (argv[0]))
267    return -1
268
269  _KERNEL_SRC = argv[1]
270
271  # syscall name, number, entry names
272
273  with open(os.path.join(_KERNEL_SRC, 'arch/arm/tools/syscall.tbl')) as tbl_file:
274    arm_syscalls = _parse_arm_syscalls(tbl_file)
275
276  with open(os.path.join(_KERNEL_SRC, 'arch/x86/entry/syscalls/syscall_32.tbl')) as tbl_file:
277    x86_syscalls = _parse_x86_syscalls(tbl_file)
278
279  with open(os.path.join(_KERNEL_SRC, 'include/uapi/asm-generic/unistd.h')) as header_file:
280    arm64_syscalls = _parse_unistd_syscalls(header_file)
281
282  with open(os.path.join(_KERNEL_SRC, 'arch/x86/entry/syscalls/syscall_64.tbl')) as tbl_file:
283    x86_64_syscalls = _parse_x86_64_syscalls(tbl_file)
284
285  with open(os.path.join(_KERNEL_SRC, 'include/linux/syscalls.h')) as header_file:
286    protos = _parse_protos(header_file)
287
288  # riscv64 syscalls are also defined by unistd.h, so we can just copy over from arm64.
289  riscv64_syscalls = arm64_syscalls
290
291  all_syscalls = {}
292
293  for arch, syscalls in [
294      ('arm', arm_syscalls),
295      ('x86', x86_syscalls),
296      ('arm64', arm64_syscalls),
297      ('x86_64', x86_64_syscalls),
298      ('riscv64', riscv64_syscalls)]:
299    for name, info in syscalls.items():
300      all_syscalls.setdefault(name, {})[arch] = info
301
302      if not protos.get(info['entry'], True):
303        all_syscalls[name][arch]['params'] = []
304
305  print(json.dumps(all_syscalls, indent=2, sort_keys=True))
306
307  return 0
308
309
310if __name__ == '__main__':
311  sys.exit(main(sys.argv))
312