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