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