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