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