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 59# 60# ARM assembler templates for each syscall stub 61# 62 63arm_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 74END(%(func)s) 75""" 76 77arm_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 94END(%(func)s) 95""" 96 97 98# 99# Arm64 assembler templates for each syscall stub 100# 101 102arm64_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 111END(%(func)s) 112""" 113 114 115# 116# MIPS assembler templates for each syscall stub 117# 118 119mips_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 1281: 129 la t9,__set_errno_internal 130 j t9 131 nop 132 .set reorder 133END(%(func)s) 134""" 135 136 137# 138# MIPS64 assembler templates for each syscall stub 139# 140 141mips64_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 1501: 151 move t0, ra 152 bal 2f 153 nop 1542: 155 .cpsetup ra, t1, 2b 156 LA t9,__set_errno_internal 157 .cpreturn 158 j t9 159 move ra, t0 160 .set pop 161END(%(func)s) 162""" 163 164 165# 166# x86 assembler templates for each syscall stub 167# 168 169x86_registers = [ "ebx", "ecx", "edx", "esi", "edi", "ebp" ] 170 171x86_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 180x86_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 1911: 192""" 193 194x86_return = """\ 195 ret 196END(%(func)s) 197""" 198 199 200# 201# x86_64 assembler templates for each syscall stub 202# 203 204x86_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 2121: 213 ret 214END(%(func)s) 215""" 216 217 218def 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 239def 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 264def 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 274def 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. 283def make__NR_name(name): 284 if name.startswith("__ARM_NR_"): 285 return name 286 else: 287 return "__NR_%s" % (name) 288 289 290def 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 304def 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 311def arm64_genstub(syscall): 312 return arm64_call % syscall 313 314 315def mips_genstub(syscall): 316 return mips_call % syscall 317 318 319def mips64_genstub(syscall): 320 return mips64_call % syscall 321 322 323def 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 354def 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 392def 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 403class 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 504 def parse_file(self, file_path): 505 logging.debug("parse_file: %s" % file_path) 506 fp = open(file_path) 507 for line in fp.xreadlines(): 508 self.lineno += 1 509 line = line.strip() 510 if not line: continue 511 if line[0] == '#': continue 512 self.parse_line(line) 513 514 fp.close() 515 516 517class 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 # Scan a Linux kernel asm/unistd.h file containing __NR_* constants 559 # and write out equivalent SYS_* constants for glibc source compatibility. 560 def scan_linux_unistd_h(self, fp, path): 561 pattern = re.compile(r'^#define __NR_([a-z]\S+) .*') 562 syscalls = set() # MIPS defines everything three times; work around that. 563 for line in open(path): 564 m = re.search(pattern, line) 565 if m: 566 syscalls.add(m.group(1)) 567 for syscall in sorted(syscalls): 568 fp.write("#define SYS_%s %s\n" % (syscall, make__NR_name(syscall))) 569 570 571 def gen_glibc_syscalls_h(self): 572 # TODO: generate a separate file for each architecture, like glibc's bits/syscall.h. 573 glibc_syscalls_h_path = "include/sys/glibc-syscalls.h" 574 logging.info("generating " + glibc_syscalls_h_path) 575 glibc_fp = create_file(glibc_syscalls_h_path) 576 glibc_fp.write("/* %s */\n" % warning) 577 glibc_fp.write("#ifndef _BIONIC_GLIBC_SYSCALLS_H_\n") 578 glibc_fp.write("#define _BIONIC_GLIBC_SYSCALLS_H_\n") 579 580 glibc_fp.write("#if defined(__aarch64__)\n") 581 self.scan_linux_unistd_h(glibc_fp, os.path.join(bionic_libc_root, "kernel/uapi/asm-generic/unistd.h")) 582 glibc_fp.write("#elif defined(__arm__)\n") 583 self.scan_linux_unistd_h(glibc_fp, os.path.join(bionic_libc_root, "kernel/uapi/asm-arm/asm/unistd.h")) 584 glibc_fp.write("#elif defined(__mips__)\n") 585 self.scan_linux_unistd_h(glibc_fp, os.path.join(bionic_libc_root, "kernel/uapi/asm-mips/asm/unistd.h")) 586 glibc_fp.write("#elif defined(__i386__)\n") 587 self.scan_linux_unistd_h(glibc_fp, os.path.join(bionic_libc_root, "kernel/uapi/asm-x86/asm/unistd_32.h")) 588 glibc_fp.write("#elif defined(__x86_64__)\n") 589 self.scan_linux_unistd_h(glibc_fp, os.path.join(bionic_libc_root, "kernel/uapi/asm-x86/asm/unistd_64.h")) 590 glibc_fp.write("#endif\n") 591 592 glibc_fp.write("#endif /* _BIONIC_GLIBC_SYSCALLS_H_ */\n") 593 glibc_fp.close() 594 self.other_files.append(glibc_syscalls_h_path) 595 596 597 # Write each syscall stub. 598 def gen_syscall_stubs(self): 599 for syscall in self.syscalls: 600 for arch in all_arches: 601 if syscall.has_key("asm-%s" % arch): 602 filename = "arch-%s/syscalls/%s.S" % (arch, syscall["func"]) 603 logging.info(">>> generating " + filename) 604 fp = create_file(filename) 605 fp.write(syscall["asm-%s" % arch]) 606 fp.close() 607 self.new_stubs.append(filename) 608 609 610 def regenerate(self): 611 logging.info("scanning for existing architecture-specific stub files...") 612 613 for arch in all_arches: 614 arch_dir = "arch-" + arch 615 logging.info("scanning " + os.path.join(bionic_libc_root, arch_dir)) 616 rel_path = os.path.join(arch_dir, "syscalls") 617 for file in os.listdir(os.path.join(bionic_libc_root, rel_path)): 618 if file.endswith(".S"): 619 self.old_stubs.append(os.path.join(rel_path, file)) 620 621 logging.info("found %d stub files" % len(self.old_stubs)) 622 623 if not os.path.exists(bionic_temp): 624 logging.info("creating %s..." % bionic_temp) 625 make_dir(bionic_temp) 626 627 logging.info("re-generating stubs and support files...") 628 629 self.gen_glibc_syscalls_h() 630 self.gen_syscall_stubs() 631 632 logging.info("comparing files...") 633 adds = [] 634 edits = [] 635 636 for stub in self.new_stubs + self.other_files: 637 tmp_file = os.path.join(bionic_temp, stub) 638 libc_file = os.path.join(bionic_libc_root, stub) 639 if not os.path.exists(libc_file): 640 # new file, git add it 641 logging.info("new file: " + stub) 642 adds.append(libc_file) 643 shutil.copyfile(tmp_file, libc_file) 644 645 elif not filecmp.cmp(tmp_file, libc_file): 646 logging.info("changed file: " + stub) 647 edits.append(stub) 648 649 deletes = [] 650 for stub in self.old_stubs: 651 if not stub in self.new_stubs: 652 logging.info("deleted file: " + stub) 653 deletes.append(os.path.join(bionic_libc_root, stub)) 654 655 if not DRY_RUN: 656 if adds: 657 commands.getoutput("git add " + " ".join(adds)) 658 if deletes: 659 commands.getoutput("git rm " + " ".join(deletes)) 660 if edits: 661 for file in edits: 662 shutil.copyfile(os.path.join(bionic_temp, file), 663 os.path.join(bionic_libc_root, file)) 664 commands.getoutput("git add " + " ".join((os.path.join(bionic_libc_root, file)) for file in edits)) 665 666 commands.getoutput("git add %s" % (os.path.join(bionic_libc_root, "SYSCALLS.TXT"))) 667 668 if (not adds) and (not deletes) and (not edits): 669 logging.info("no changes detected!") 670 else: 671 logging.info("ready to go!!") 672 673logging.basicConfig(level=logging.INFO) 674 675state = State() 676state.process_file(os.path.join(bionic_libc_root, "SYSCALLS.TXT")) 677state.regenerate() 678