1#!/usr/bin/env python3 2 3# This tool is used to generate the assembler system call stubs, 4# the header files listing all available system calls, and the 5# makefiles used to build all the stubs. 6 7import atexit 8import filecmp 9import glob 10import re 11import shutil 12import stat 13import string 14import sys 15import tempfile 16 17 18SupportedArchitectures = [ "arm", "arm64", "riscv64", "x86", "x86_64" ] 19 20syscall_stub_header = \ 21""" 22ENTRY(%(func)s) 23""" 24 25 26# 27# ARM assembler templates for each syscall stub 28# 29 30arm_eabi_call_default = syscall_stub_header + """\ 31 mov ip, r7 32 .cfi_register r7, ip 33 ldr r7, =%(__NR_name)s 34 swi #0 35 mov r7, ip 36 .cfi_restore r7 37 cmn r0, #(MAX_ERRNO + 1) 38 bxls lr 39 neg r0, r0 40 b __set_errno_internal 41END(%(func)s) 42""" 43 44arm_eabi_call_long = syscall_stub_header + """\ 45 mov ip, sp 46 stmfd sp!, {r4, r5, r6, r7} 47 .cfi_def_cfa_offset 16 48 .cfi_rel_offset r4, 0 49 .cfi_rel_offset r5, 4 50 .cfi_rel_offset r6, 8 51 .cfi_rel_offset r7, 12 52 ldmfd ip, {r4, r5, r6} 53 ldr r7, =%(__NR_name)s 54 swi #0 55 ldmfd sp!, {r4, r5, r6, r7} 56 .cfi_def_cfa_offset 0 57 cmn r0, #(MAX_ERRNO + 1) 58 bxls lr 59 neg r0, r0 60 b __set_errno_internal 61END(%(func)s) 62""" 63 64 65# 66# Arm64 assembler template for each syscall stub 67# 68 69arm64_call = syscall_stub_header + """\ 70 mov x8, %(__NR_name)s 71 svc #0 72 73 cmn x0, #(MAX_ERRNO + 1) 74 cneg x0, x0, hi 75 b.hi __set_errno_internal 76 77 ret 78END(%(func)s) 79""" 80 81 82# 83# RISC-V64 assembler templates for each syscall stub 84# 85 86riscv64_call = syscall_stub_header + """\ 87 li a7, %(__NR_name)s 88 ecall 89 90 li a7, -MAX_ERRNO 91 bgtu a0, a7, 1f 92 93 ret 941: 95 neg a0, a0 96 tail __set_errno_internal 97END(%(func)s) 98""" 99 100# 101# x86 assembler templates for each syscall stub 102# 103 104x86_registers = [ "ebx", "ecx", "edx", "esi", "edi", "ebp" ] 105 106x86_call_prepare = """\ 107 108 call __kernel_syscall 109 pushl %eax 110 .cfi_adjust_cfa_offset 4 111 .cfi_rel_offset eax, 0 112 113""" 114 115x86_call = """\ 116 movl $%(__NR_name)s, %%eax 117 call *(%%esp) 118 addl $4, %%esp 119 120 cmpl $-MAX_ERRNO, %%eax 121 jb 1f 122 negl %%eax 123 pushl %%eax 124 call __set_errno_internal 125 addl $4, %%esp 1261: 127""" 128 129x86_return = """\ 130 ret 131END(%(func)s) 132""" 133 134 135# 136# x86_64 assembler template for each syscall stub 137# 138 139x86_64_call = """\ 140 movl $%(__NR_name)s, %%eax 141 syscall 142 cmpq $-MAX_ERRNO, %%rax 143 jb 1f 144 negl %%eax 145 movl %%eax, %%edi 146 call __set_errno_internal 1471: 148 ret 149END(%(func)s) 150""" 151 152 153def param_uses_64bits(param): 154 """Returns True iff a syscall parameter description corresponds 155 to a 64-bit type.""" 156 param = param.strip() 157 # First, check that the param type begins with one of the known 158 # 64-bit types. 159 if not ( \ 160 param.startswith("int64_t") or param.startswith("uint64_t") or \ 161 param.startswith("loff_t") or param.startswith("off64_t") or \ 162 param.startswith("long long") or param.startswith("unsigned long long") or 163 param.startswith("signed long long") ): 164 return False 165 166 # Second, check that there is no pointer type here 167 if param.find("*") >= 0: 168 return False 169 170 # Ok 171 return True 172 173 174def count_arm_param_registers(params): 175 """This function is used to count the number of register used 176 to pass parameters when invoking an ARM system call. 177 This is because the ARM EABI mandates that 64-bit quantities 178 must be passed in an even+odd register pair. So, for example, 179 something like: 180 181 foo(int fd, off64_t pos) 182 183 would actually need 4 registers: 184 r0 -> int 185 r1 -> unused 186 r2-r3 -> pos 187 """ 188 count = 0 189 for param in params: 190 if param_uses_64bits(param): 191 if (count & 1) != 0: 192 count += 1 193 count += 2 194 else: 195 count += 1 196 return count 197 198 199def count_generic_param_registers(params): 200 count = 0 201 for param in params: 202 if param_uses_64bits(param): 203 count += 2 204 else: 205 count += 1 206 return count 207 208 209def count_generic_param_registers64(params): 210 count = 0 211 for param in params: 212 count += 1 213 return count 214 215 216# This lets us support regular system calls like __NR_write and also weird 217# ones like __ARM_NR_cacheflush, where the NR doesn't come at the start. 218def make__NR_name(name): 219 if name.startswith("__ARM_NR_"): 220 return name 221 else: 222 return "__NR_%s" % (name) 223 224 225def add_footer(pointer_length, stub, syscall): 226 # Add any aliases for this syscall. 227 aliases = syscall["aliases"] 228 for alias in aliases: 229 stub += "\nALIAS_SYMBOL(%s, %s)\n" % (alias, syscall["func"]) 230 231 # Use hidden visibility on LP64 for any functions beginning with underscores. 232 if pointer_length == 64 and syscall["func"].startswith("__"): 233 stub += '.hidden ' + syscall["func"] + '\n' 234 235 return stub 236 237 238def arm_eabi_genstub(syscall): 239 num_regs = count_arm_param_registers(syscall["params"]) 240 if num_regs > 4: 241 return arm_eabi_call_long % syscall 242 return arm_eabi_call_default % syscall 243 244 245def arm64_genstub(syscall): 246 return arm64_call % syscall 247 248 249def riscv64_genstub(syscall): 250 return riscv64_call % syscall 251 252 253def x86_genstub(syscall): 254 result = syscall_stub_header % syscall 255 256 numparams = count_generic_param_registers(syscall["params"]) 257 stack_bias = numparams*4 + 8 258 offset = 0 259 mov_result = "" 260 first_push = True 261 for register in x86_registers[:numparams]: 262 result += " pushl %%%s\n" % register 263 if first_push: 264 result += " .cfi_def_cfa_offset 8\n" 265 result += " .cfi_rel_offset %s, 0\n" % register 266 first_push = False 267 else: 268 result += " .cfi_adjust_cfa_offset 4\n" 269 result += " .cfi_rel_offset %s, 0\n" % register 270 mov_result += " mov %d(%%esp), %%%s\n" % (stack_bias+offset, register) 271 offset += 4 272 273 result += x86_call_prepare 274 result += mov_result 275 result += x86_call % syscall 276 277 for register in reversed(x86_registers[:numparams]): 278 result += " popl %%%s\n" % register 279 280 result += x86_return % syscall 281 return result 282 283 284def x86_genstub_socketcall(syscall): 285 # %ebx <--- Argument 1 - The call id of the needed vectored 286 # syscall (socket, bind, recv, etc) 287 # %ecx <--- Argument 2 - Pointer to the rest of the arguments 288 # from the original function called (socket()) 289 290 result = syscall_stub_header % syscall 291 292 # save the regs we need 293 result += " pushl %ebx\n" 294 result += " .cfi_def_cfa_offset 8\n" 295 result += " .cfi_rel_offset ebx, 0\n" 296 result += " pushl %ecx\n" 297 result += " .cfi_adjust_cfa_offset 4\n" 298 result += " .cfi_rel_offset ecx, 0\n" 299 stack_bias = 16 300 301 result += x86_call_prepare 302 303 # set the call id (%ebx) 304 result += " mov $%d, %%ebx\n" % syscall["socketcall_id"] 305 306 # set the pointer to the rest of the args into %ecx 307 result += " mov %esp, %ecx\n" 308 result += " addl $%d, %%ecx\n" % (stack_bias) 309 310 # now do the syscall code itself 311 result += x86_call % syscall 312 313 # now restore the saved regs 314 result += " popl %ecx\n" 315 result += " popl %ebx\n" 316 317 # epilog 318 result += x86_return % syscall 319 return result 320 321 322def x86_64_genstub(syscall): 323 result = syscall_stub_header % syscall 324 num_regs = count_generic_param_registers64(syscall["params"]) 325 if (num_regs > 3): 326 # rcx is used as 4th argument. Kernel wants it at r10. 327 result += " movq %rcx, %r10\n" 328 329 result += x86_64_call % syscall 330 return result 331 332 333class SysCallsTxtParser: 334 def __init__(self): 335 self.syscalls = [] 336 self.lineno = 0 337 self.errors = False 338 339 def E(self, msg): 340 print("%d: %s" % (self.lineno, msg)) 341 self.errors = True 342 343 def parse_line(self, line): 344 """ parse a syscall spec line. 345 346 line processing, format is 347 return type func_name[|alias_list][:syscall_name[:socketcall_id]] ( [paramlist] ) architecture_list 348 """ 349 pos_lparen = line.find('(') 350 E = self.E 351 if pos_lparen < 0: 352 E("missing left parenthesis in '%s'" % line) 353 return 354 355 pos_rparen = line.rfind(')') 356 if pos_rparen < 0 or pos_rparen <= pos_lparen: 357 E("missing or misplaced right parenthesis in '%s'" % line) 358 return 359 360 return_type = line[:pos_lparen].strip().split() 361 if len(return_type) < 2: 362 E("missing return type in '%s'" % line) 363 return 364 365 syscall_func = return_type[-1] 366 return_type = ' '.join(return_type[:-1]) 367 socketcall_id = -1 368 369 pos_colon = syscall_func.find(':') 370 if pos_colon < 0: 371 syscall_name = syscall_func 372 else: 373 if pos_colon == 0 or pos_colon+1 >= len(syscall_func): 374 E("misplaced colon in '%s'" % line) 375 return 376 377 # now find if there is a socketcall_id for a dispatch-type syscall 378 # after the optional 2nd colon 379 pos_colon2 = syscall_func.find(':', pos_colon + 1) 380 if pos_colon2 < 0: 381 syscall_name = syscall_func[pos_colon+1:] 382 syscall_func = syscall_func[:pos_colon] 383 else: 384 if pos_colon2+1 >= len(syscall_func): 385 E("misplaced colon2 in '%s'" % line) 386 return 387 syscall_name = syscall_func[(pos_colon+1):pos_colon2] 388 socketcall_id = int(syscall_func[pos_colon2+1:]) 389 syscall_func = syscall_func[:pos_colon] 390 391 alias_delim = syscall_func.find('|') 392 if alias_delim > 0: 393 alias_list = syscall_func[alias_delim+1:].strip() 394 syscall_func = syscall_func[:alias_delim] 395 alias_delim = syscall_name.find('|') 396 if alias_delim > 0: 397 syscall_name = syscall_name[:alias_delim] 398 syscall_aliases = alias_list.split(',') 399 else: 400 syscall_aliases = [] 401 402 if pos_rparen > pos_lparen+1: 403 syscall_params = line[pos_lparen+1:pos_rparen].split(',') 404 params = ','.join(syscall_params) 405 else: 406 syscall_params = [] 407 params = "void" 408 409 t = { 410 "name" : syscall_name, 411 "func" : syscall_func, 412 "aliases" : syscall_aliases, 413 "params" : syscall_params, 414 "decl" : "%-15s %s (%s);" % (return_type, syscall_func, params), 415 "socketcall_id" : socketcall_id 416 } 417 418 # Parse the architecture list. 419 arch_list = line[pos_rparen+1:].strip() 420 if arch_list == "all": 421 for arch in SupportedArchitectures: 422 t[arch] = True 423 else: 424 for arch in arch_list.split(','): 425 if arch == "lp32": 426 for arch in SupportedArchitectures: 427 if "64" not in arch: 428 t[arch] = True 429 elif arch == "lp64": 430 for arch in SupportedArchitectures: 431 if "64" in arch: 432 t[arch] = True 433 elif arch in SupportedArchitectures: 434 t[arch] = True 435 else: 436 E("invalid syscall architecture '%s' in '%s'" % (arch, line)) 437 return 438 439 self.syscalls.append(t) 440 441 def parse_open_file(self, fp): 442 for line in fp: 443 self.lineno += 1 444 line = line.strip() 445 if not line: continue 446 if line[0] == '#': continue 447 self.parse_line(line) 448 if self.errors: 449 sys.exit(1) 450 451 def parse_file(self, file_path): 452 with open(file_path) as fp: 453 self.parse_open_file(fp) 454 455 456def main(arch, syscall_file): 457 parser = SysCallsTxtParser() 458 parser.parse_file(syscall_file) 459 460 for syscall in parser.syscalls: 461 syscall["__NR_name"] = make__NR_name(syscall["name"]) 462 463 if "arm" in syscall: 464 syscall["asm-arm"] = add_footer(32, arm_eabi_genstub(syscall), syscall) 465 466 if "arm64" in syscall: 467 syscall["asm-arm64"] = add_footer(64, arm64_genstub(syscall), syscall) 468 469 if "riscv64" in syscall: 470 syscall["asm-riscv64"] = add_footer(64, riscv64_genstub(syscall), syscall) 471 472 if "x86" in syscall: 473 if syscall["socketcall_id"] >= 0: 474 syscall["asm-x86"] = add_footer(32, x86_genstub_socketcall(syscall), syscall) 475 else: 476 syscall["asm-x86"] = add_footer(32, x86_genstub(syscall), syscall) 477 elif syscall["socketcall_id"] >= 0: 478 E("socketcall_id for dispatch syscalls is only supported for x86 in '%s'" % t) 479 return 480 481 if "x86_64" in syscall: 482 syscall["asm-x86_64"] = add_footer(64, x86_64_genstub(syscall), syscall) 483 484 print("/* Generated by gensyscalls.py. Do not edit. */\n") 485 print("#include <private/bionic_asm.h>\n") 486 for syscall in parser.syscalls: 487 if ("asm-%s" % arch) in syscall: 488 print(syscall["asm-%s" % arch]) 489 490 if arch == 'arm64': 491 print('\nNOTE_GNU_PROPERTY()\n') 492 493if __name__ == "__main__": 494 if len(sys.argv) < 2: 495 print("Usage: gensyscalls.py ARCH SOURCE_FILE") 496 sys.exit(1) 497 498 arch = sys.argv[1] 499 syscall_file = sys.argv[2] 500 main(arch, syscall_file) 501