gensyscalls.py revision 011e111d299284b65af07add523a9dccac356244
1#!/usr/bin/python 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 commands 9import filecmp 10import glob 11import logging 12import os.path 13import re 14import shutil 15import stat 16import string 17import sys 18import tempfile 19 20 21all_arches = [ "arm", "arm64", "mips", "mips64", "x86", "x86_64" ] 22 23 24# temp directory where we store all intermediate files 25bionic_temp = tempfile.mkdtemp(prefix="bionic_gensyscalls"); 26# Make sure the directory is deleted when the script exits. 27atexit.register(shutil.rmtree, bionic_temp) 28 29bionic_libc_root = os.path.join(os.environ["ANDROID_BUILD_TOP"], "bionic/libc") 30 31warning = "Generated by gensyscalls.py. Do not edit." 32 33DRY_RUN = False 34 35def make_dir(path): 36 path = os.path.abspath(path) 37 if not os.path.exists(path): 38 parent = os.path.dirname(path) 39 if parent: 40 make_dir(parent) 41 os.mkdir(path) 42 43 44def create_file(relpath): 45 full_path = os.path.join(bionic_temp, relpath) 46 dir = os.path.dirname(full_path) 47 make_dir(dir) 48 return open(full_path, "w") 49 50 51syscall_stub_header = "/* " + warning + " */\n" + \ 52""" 53#include <private/bionic_asm.h> 54 55ENTRY(%(func)s) 56""" 57 58 59function_alias = """ 60 .globl %(alias)s 61 .equ %(alias)s, %(func)s 62""" 63 64 65# 66# ARM assembler templates for each syscall stub 67# 68 69arm_eabi_call_default = syscall_stub_header + """\ 70 mov ip, r7 71 ldr r7, =%(__NR_name)s 72 swi #0 73 mov r7, ip 74 cmn r0, #(MAX_ERRNO + 1) 75 bxls lr 76 neg r0, r0 77 b __set_errno_internal 78END(%(func)s) 79""" 80 81arm_eabi_call_long = syscall_stub_header + """\ 82 mov ip, sp 83 stmfd sp!, {r4, r5, r6, r7} 84 .cfi_def_cfa_offset 16 85 .cfi_rel_offset r4, 0 86 .cfi_rel_offset r5, 4 87 .cfi_rel_offset r6, 8 88 .cfi_rel_offset r7, 12 89 ldmfd ip, {r4, r5, r6} 90 ldr r7, =%(__NR_name)s 91 swi #0 92 ldmfd sp!, {r4, r5, r6, r7} 93 .cfi_def_cfa_offset 0 94 cmn r0, #(MAX_ERRNO + 1) 95 bxls lr 96 neg r0, r0 97 b __set_errno_internal 98END(%(func)s) 99""" 100 101 102# 103# Arm64 assembler templates for each syscall stub 104# 105 106arm64_call = syscall_stub_header + """\ 107 mov x8, %(__NR_name)s 108 svc #0 109 110 cmn x0, #(MAX_ERRNO + 1) 111 cneg x0, x0, hi 112 b.hi __set_errno_internal 113 114 ret 115END(%(func)s) 116""" 117 118 119# 120# MIPS assembler templates for each syscall stub 121# 122 123mips_call = syscall_stub_header + """\ 124 .set noreorder 125 .cpload t9 126 li v0, %(__NR_name)s 127 syscall 128 bnez a3, 1f 129 move a0, v0 130 j ra 131 nop 1321: 133 la t9,__set_errno_internal 134 j t9 135 nop 136 .set reorder 137END(%(func)s) 138""" 139 140 141# 142# MIPS64 assembler templates for each syscall stub 143# 144 145mips64_call = syscall_stub_header + """\ 146 .set push 147 .set noreorder 148 li v0, %(__NR_name)s 149 syscall 150 bnez a3, 1f 151 move a0, v0 152 j ra 153 nop 1541: 155 move t0, ra 156 bal 2f 157 nop 1582: 159 .cpsetup ra, t1, 2b 160 LA t9,__set_errno_internal 161 .cpreturn 162 j t9 163 move ra, t0 164 .set pop 165END(%(func)s) 166""" 167 168 169# 170# x86 assembler templates for each syscall stub 171# 172 173x86_registers = [ "ebx", "ecx", "edx", "esi", "edi", "ebp" ] 174 175x86_call = """\ 176 movl $%(__NR_name)s, %%eax 177 int $0x80 178 cmpl $-MAX_ERRNO, %%eax 179 jb 1f 180 negl %%eax 181 pushl %%eax 182 call __set_errno_internal 183 addl $4, %%esp 1841: 185""" 186 187x86_return = """\ 188 ret 189END(%(func)s) 190""" 191 192 193# 194# x86_64 assembler templates for each syscall stub 195# 196 197x86_64_call = """\ 198 movl $%(__NR_name)s, %%eax 199 syscall 200 cmpq $-MAX_ERRNO, %%rax 201 jb 1f 202 negl %%eax 203 movl %%eax, %%edi 204 call __set_errno_internal 2051: 206 ret 207END(%(func)s) 208""" 209 210 211def param_uses_64bits(param): 212 """Returns True iff a syscall parameter description corresponds 213 to a 64-bit type.""" 214 param = param.strip() 215 # First, check that the param type begins with one of the known 216 # 64-bit types. 217 if not ( \ 218 param.startswith("int64_t") or param.startswith("uint64_t") or \ 219 param.startswith("loff_t") or param.startswith("off64_t") or \ 220 param.startswith("long long") or param.startswith("unsigned long long") or 221 param.startswith("signed long long") ): 222 return False 223 224 # Second, check that there is no pointer type here 225 if param.find("*") >= 0: 226 return False 227 228 # Ok 229 return True 230 231 232def count_arm_param_registers(params): 233 """This function is used to count the number of register used 234 to pass parameters when invoking an ARM system call. 235 This is because the ARM EABI mandates that 64-bit quantities 236 must be passed in an even+odd register pair. So, for example, 237 something like: 238 239 foo(int fd, off64_t pos) 240 241 would actually need 4 registers: 242 r0 -> int 243 r1 -> unused 244 r2-r3 -> pos 245 """ 246 count = 0 247 for param in params: 248 if param_uses_64bits(param): 249 if (count & 1) != 0: 250 count += 1 251 count += 2 252 else: 253 count += 1 254 return count 255 256 257def count_generic_param_registers(params): 258 count = 0 259 for param in params: 260 if param_uses_64bits(param): 261 count += 2 262 else: 263 count += 1 264 return count 265 266 267def count_generic_param_registers64(params): 268 count = 0 269 for param in params: 270 count += 1 271 return count 272 273 274# This lets us support regular system calls like __NR_write and also weird 275# ones like __ARM_NR_cacheflush, where the NR doesn't come at the start. 276def make__NR_name(name): 277 if name.startswith("__ARM_NR_"): 278 return name 279 else: 280 return "__NR_%s" % (name) 281 282 283def add_footer(pointer_length, stub, syscall): 284 # Add any aliases for this syscall. 285 aliases = syscall["aliases"] 286 for alias in aliases: 287 stub += function_alias % { "func" : syscall["func"], "alias" : alias } 288 289 # Use hidden visibility for any functions beginning with underscores. 290 if pointer_length == 64 and syscall["func"].startswith("__"): 291 stub += '.hidden ' + syscall["func"] + '\n' 292 293 return stub 294 295 296def arm_eabi_genstub(syscall): 297 num_regs = count_arm_param_registers(syscall["params"]) 298 if num_regs > 4: 299 return arm_eabi_call_long % syscall 300 return arm_eabi_call_default % syscall 301 302 303def arm64_genstub(syscall): 304 return arm64_call % syscall 305 306 307def mips_genstub(syscall): 308 return mips_call % syscall 309 310 311def mips64_genstub(syscall): 312 return mips64_call % syscall 313 314 315def x86_genstub(syscall): 316 result = syscall_stub_header % syscall 317 318 numparams = count_generic_param_registers(syscall["params"]) 319 stack_bias = numparams*4 + 4 320 offset = 0 321 mov_result = "" 322 first_push = True 323 for register in x86_registers[:numparams]: 324 result += " pushl %%%s\n" % register 325 if first_push: 326 result += " .cfi_def_cfa_offset 8\n" 327 result += " .cfi_rel_offset %s, 0\n" % register 328 first_push = False 329 else: 330 result += " .cfi_adjust_cfa_offset 4\n" 331 result += " .cfi_rel_offset %s, 0\n" % register 332 mov_result += " mov %d(%%esp), %%%s\n" % (stack_bias+offset, register) 333 offset += 4 334 335 result += mov_result 336 result += x86_call % syscall 337 338 for register in reversed(x86_registers[:numparams]): 339 result += " popl %%%s\n" % register 340 341 result += x86_return % syscall 342 return result 343 344 345def x86_genstub_socketcall(syscall): 346 # %ebx <--- Argument 1 - The call id of the needed vectored 347 # syscall (socket, bind, recv, etc) 348 # %ecx <--- Argument 2 - Pointer to the rest of the arguments 349 # from the original function called (socket()) 350 351 result = syscall_stub_header % syscall 352 353 # save the regs we need 354 result += " pushl %ebx\n" 355 result += " .cfi_def_cfa_offset 8\n" 356 result += " .cfi_rel_offset ebx, 0\n" 357 result += " pushl %ecx\n" 358 result += " .cfi_adjust_cfa_offset 4\n" 359 result += " .cfi_rel_offset ecx, 0\n" 360 stack_bias = 12 361 362 # set the call id (%ebx) 363 result += " mov $%d, %%ebx\n" % syscall["socketcall_id"] 364 365 # set the pointer to the rest of the args into %ecx 366 result += " mov %esp, %ecx\n" 367 result += " addl $%d, %%ecx\n" % (stack_bias) 368 369 # now do the syscall code itself 370 result += x86_call % syscall 371 372 # now restore the saved regs 373 result += " popl %ecx\n" 374 result += " popl %ebx\n" 375 376 # epilog 377 result += x86_return % syscall 378 return result 379 380 381def x86_64_genstub(syscall): 382 result = syscall_stub_header % syscall 383 num_regs = count_generic_param_registers64(syscall["params"]) 384 if (num_regs > 3): 385 # rcx is used as 4th argument. Kernel wants it at r10. 386 result += " movq %rcx, %r10\n" 387 388 result += x86_64_call % syscall 389 return result 390 391 392class SysCallsTxtParser: 393 def __init__(self): 394 self.syscalls = [] 395 self.lineno = 0 396 397 def E(self, msg): 398 print "%d: %s" % (self.lineno, msg) 399 400 def parse_line(self, line): 401 """ parse a syscall spec line. 402 403 line processing, format is 404 return type func_name[|alias_list][:syscall_name[:socketcall_id]] ( [paramlist] ) architecture_list 405 """ 406 pos_lparen = line.find('(') 407 E = self.E 408 if pos_lparen < 0: 409 E("missing left parenthesis in '%s'" % line) 410 return 411 412 pos_rparen = line.rfind(')') 413 if pos_rparen < 0 or pos_rparen <= pos_lparen: 414 E("missing or misplaced right parenthesis in '%s'" % line) 415 return 416 417 return_type = line[:pos_lparen].strip().split() 418 if len(return_type) < 2: 419 E("missing return type in '%s'" % line) 420 return 421 422 syscall_func = return_type[-1] 423 return_type = string.join(return_type[:-1],' ') 424 socketcall_id = -1 425 426 pos_colon = syscall_func.find(':') 427 if pos_colon < 0: 428 syscall_name = syscall_func 429 else: 430 if pos_colon == 0 or pos_colon+1 >= len(syscall_func): 431 E("misplaced colon in '%s'" % line) 432 return 433 434 # now find if there is a socketcall_id for a dispatch-type syscall 435 # after the optional 2nd colon 436 pos_colon2 = syscall_func.find(':', pos_colon + 1) 437 if pos_colon2 < 0: 438 syscall_name = syscall_func[pos_colon+1:] 439 syscall_func = syscall_func[:pos_colon] 440 else: 441 if pos_colon2+1 >= len(syscall_func): 442 E("misplaced colon2 in '%s'" % line) 443 return 444 syscall_name = syscall_func[(pos_colon+1):pos_colon2] 445 socketcall_id = int(syscall_func[pos_colon2+1:]) 446 syscall_func = syscall_func[:pos_colon] 447 448 alias_delim = syscall_func.find('|') 449 if alias_delim > 0: 450 alias_list = syscall_func[alias_delim+1:].strip() 451 syscall_func = syscall_func[:alias_delim] 452 alias_delim = syscall_name.find('|') 453 if alias_delim > 0: 454 syscall_name = syscall_name[:alias_delim] 455 syscall_aliases = string.split(alias_list, ',') 456 else: 457 syscall_aliases = [] 458 459 if pos_rparen > pos_lparen+1: 460 syscall_params = line[pos_lparen+1:pos_rparen].split(',') 461 params = string.join(syscall_params,',') 462 else: 463 syscall_params = [] 464 params = "void" 465 466 t = { 467 "name" : syscall_name, 468 "func" : syscall_func, 469 "aliases" : syscall_aliases, 470 "params" : syscall_params, 471 "decl" : "%-15s %s (%s);" % (return_type, syscall_func, params), 472 "socketcall_id" : socketcall_id 473 } 474 475 # Parse the architecture list. 476 arch_list = line[pos_rparen+1:].strip() 477 if arch_list == "all": 478 for arch in all_arches: 479 t[arch] = True 480 else: 481 for arch in string.split(arch_list, ','): 482 if arch in all_arches: 483 t[arch] = True 484 else: 485 E("invalid syscall architecture '%s' in '%s'" % (arch, line)) 486 return 487 488 self.syscalls.append(t) 489 490 logging.debug(t) 491 492 493 def parse_file(self, file_path): 494 logging.debug("parse_file: %s" % file_path) 495 fp = open(file_path) 496 for line in fp.xreadlines(): 497 self.lineno += 1 498 line = line.strip() 499 if not line: continue 500 if line[0] == '#': continue 501 self.parse_line(line) 502 503 fp.close() 504 505 506class State: 507 def __init__(self): 508 self.old_stubs = [] 509 self.new_stubs = [] 510 self.other_files = [] 511 self.syscalls = [] 512 513 514 def process_file(self, input): 515 parser = SysCallsTxtParser() 516 parser.parse_file(input) 517 self.syscalls = parser.syscalls 518 parser = None 519 520 for syscall in self.syscalls: 521 syscall["__NR_name"] = make__NR_name(syscall["name"]) 522 523 if syscall.has_key("arm"): 524 syscall["asm-arm"] = add_footer(32, arm_eabi_genstub(syscall), syscall) 525 526 if syscall.has_key("arm64"): 527 syscall["asm-arm64"] = add_footer(64, arm64_genstub(syscall), syscall) 528 529 if syscall.has_key("x86"): 530 if syscall["socketcall_id"] >= 0: 531 syscall["asm-x86"] = add_footer(32, x86_genstub_socketcall(syscall), syscall) 532 else: 533 syscall["asm-x86"] = add_footer(32, x86_genstub(syscall), syscall) 534 elif syscall["socketcall_id"] >= 0: 535 E("socketcall_id for dispatch syscalls is only supported for x86 in '%s'" % t) 536 return 537 538 if syscall.has_key("mips"): 539 syscall["asm-mips"] = add_footer(32, mips_genstub(syscall), syscall) 540 541 if syscall.has_key("mips64"): 542 syscall["asm-mips64"] = add_footer(64, mips64_genstub(syscall), syscall) 543 544 if syscall.has_key("x86_64"): 545 syscall["asm-x86_64"] = add_footer(64, x86_64_genstub(syscall), syscall) 546 547 # Scan a Linux kernel asm/unistd.h file containing __NR_* constants 548 # and write out equivalent SYS_* constants for glibc source compatibility. 549 def scan_linux_unistd_h(self, fp, path): 550 pattern = re.compile(r'^#define __NR_([a-z]\S+) .*') 551 syscalls = set() # MIPS defines everything three times; work around that. 552 for line in open(path): 553 m = re.search(pattern, line) 554 if m: 555 syscalls.add(m.group(1)) 556 for syscall in sorted(syscalls): 557 fp.write("#define SYS_%s %s\n" % (syscall, make__NR_name(syscall))) 558 559 560 def gen_glibc_syscalls_h(self): 561 # TODO: generate a separate file for each architecture, like glibc's bits/syscall.h. 562 glibc_syscalls_h_path = "include/sys/glibc-syscalls.h" 563 logging.info("generating " + glibc_syscalls_h_path) 564 glibc_fp = create_file(glibc_syscalls_h_path) 565 glibc_fp.write("/* %s */\n" % warning) 566 glibc_fp.write("#ifndef _BIONIC_GLIBC_SYSCALLS_H_\n") 567 glibc_fp.write("#define _BIONIC_GLIBC_SYSCALLS_H_\n") 568 569 glibc_fp.write("#if defined(__aarch64__)\n") 570 self.scan_linux_unistd_h(glibc_fp, os.path.join(bionic_libc_root, "kernel/uapi/asm-generic/unistd.h")) 571 glibc_fp.write("#elif defined(__arm__)\n") 572 self.scan_linux_unistd_h(glibc_fp, os.path.join(bionic_libc_root, "kernel/uapi/asm-arm/asm/unistd.h")) 573 glibc_fp.write("#elif defined(__mips__)\n") 574 self.scan_linux_unistd_h(glibc_fp, os.path.join(bionic_libc_root, "kernel/uapi/asm-mips/asm/unistd.h")) 575 glibc_fp.write("#elif defined(__i386__)\n") 576 self.scan_linux_unistd_h(glibc_fp, os.path.join(bionic_libc_root, "kernel/uapi/asm-x86/asm/unistd_32.h")) 577 glibc_fp.write("#elif defined(__x86_64__)\n") 578 self.scan_linux_unistd_h(glibc_fp, os.path.join(bionic_libc_root, "kernel/uapi/asm-x86/asm/unistd_64.h")) 579 glibc_fp.write("#endif\n") 580 581 glibc_fp.write("#endif /* _BIONIC_GLIBC_SYSCALLS_H_ */\n") 582 glibc_fp.close() 583 self.other_files.append(glibc_syscalls_h_path) 584 585 586 # Write each syscall stub. 587 def gen_syscall_stubs(self): 588 for syscall in self.syscalls: 589 for arch in all_arches: 590 if syscall.has_key("asm-%s" % arch): 591 filename = "arch-%s/syscalls/%s.S" % (arch, syscall["func"]) 592 logging.info(">>> generating " + filename) 593 fp = create_file(filename) 594 fp.write(syscall["asm-%s" % arch]) 595 fp.close() 596 self.new_stubs.append(filename) 597 598 599 def regenerate(self): 600 logging.info("scanning for existing architecture-specific stub files...") 601 602 for arch in all_arches: 603 arch_dir = "arch-" + arch 604 logging.info("scanning " + os.path.join(bionic_libc_root, arch_dir)) 605 rel_path = os.path.join(arch_dir, "syscalls") 606 for file in os.listdir(os.path.join(bionic_libc_root, rel_path)): 607 if file.endswith(".S"): 608 self.old_stubs.append(os.path.join(rel_path, file)) 609 610 logging.info("found %d stub files" % len(self.old_stubs)) 611 612 if not os.path.exists(bionic_temp): 613 logging.info("creating %s..." % bionic_temp) 614 make_dir(bionic_temp) 615 616 logging.info("re-generating stubs and support files...") 617 618 self.gen_glibc_syscalls_h() 619 self.gen_syscall_stubs() 620 621 logging.info("comparing files...") 622 adds = [] 623 edits = [] 624 625 for stub in self.new_stubs + self.other_files: 626 tmp_file = os.path.join(bionic_temp, stub) 627 libc_file = os.path.join(bionic_libc_root, stub) 628 if not os.path.exists(libc_file): 629 # new file, git add it 630 logging.info("new file: " + stub) 631 adds.append(libc_file) 632 shutil.copyfile(tmp_file, libc_file) 633 634 elif not filecmp.cmp(tmp_file, libc_file): 635 logging.info("changed file: " + stub) 636 edits.append(stub) 637 638 deletes = [] 639 for stub in self.old_stubs: 640 if not stub in self.new_stubs: 641 logging.info("deleted file: " + stub) 642 deletes.append(os.path.join(bionic_libc_root, stub)) 643 644 if not DRY_RUN: 645 if adds: 646 commands.getoutput("git add " + " ".join(adds)) 647 if deletes: 648 commands.getoutput("git rm " + " ".join(deletes)) 649 if edits: 650 for file in edits: 651 shutil.copyfile(os.path.join(bionic_temp, file), 652 os.path.join(bionic_libc_root, file)) 653 commands.getoutput("git add " + " ".join((os.path.join(bionic_libc_root, file)) for file in edits)) 654 655 commands.getoutput("git add %s" % (os.path.join(bionic_libc_root, "SYSCALLS.TXT"))) 656 657 if (not adds) and (not deletes) and (not edits): 658 logging.info("no changes detected!") 659 else: 660 logging.info("ready to go!!") 661 662logging.basicConfig(level=logging.INFO) 663 664state = State() 665state.process_file(os.path.join(bionic_libc_root, "SYSCALLS.TXT")) 666state.regenerate() 667