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