gensyscalls.py revision 986f9067103e41296898a7c0e89f5ed84420fb5b
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 commands 8import filecmp 9import glob 10import os.path 11import re 12import shutil 13import stat 14import sys 15 16from bionic_utils import * 17 18bionic_libc_root = os.environ["ANDROID_BUILD_TOP"] + "/bionic/libc/" 19 20# temp directory where we store all intermediate files 21bionic_temp = "/tmp/bionic_gensyscalls/" 22 23warning = "Generated by gensyscalls.py. Do not edit." 24 25DRY_RUN = False 26 27def make_dir(path): 28 path = os.path.abspath(path) 29 if not os.path.exists(path): 30 parent = os.path.dirname(path) 31 if parent: 32 make_dir(parent) 33 os.mkdir(path) 34 35 36def create_file(relpath): 37 dir = os.path.dirname(bionic_temp + relpath) 38 make_dir(dir) 39 return open(bionic_temp + relpath, "w") 40 41 42syscall_stub_header = "/* " + warning + " */\n" + \ 43""" 44#include <private/bionic_asm.h> 45 46ENTRY(%(func)s) 47""" 48 49 50function_alias = """ 51 .globl %(alias)s 52 .equ %(alias)s, %(func)s 53""" 54 55 56# 57# ARM assembler templates for each syscall stub 58# 59 60arm_eabi_call_default = syscall_stub_header + """\ 61 mov ip, r7 62 ldr r7, =%(__NR_name)s 63 swi #0 64 mov r7, ip 65 cmn r0, #(MAX_ERRNO + 1) 66 bxls lr 67 neg r0, r0 68 b __set_errno 69END(%(func)s) 70""" 71 72arm_eabi_call_long = syscall_stub_header + """\ 73 mov ip, sp 74 stmfd sp!, {r4, r5, r6, r7} 75 .cfi_def_cfa_offset 16 76 .cfi_rel_offset r4, 0 77 .cfi_rel_offset r5, 4 78 .cfi_rel_offset r6, 8 79 .cfi_rel_offset r7, 12 80 ldmfd ip, {r4, r5, r6} 81 ldr r7, =%(__NR_name)s 82 swi #0 83 ldmfd sp!, {r4, r5, r6, r7} 84 .cfi_def_cfa_offset 0 85 cmn r0, #(MAX_ERRNO + 1) 86 bxls lr 87 neg r0, r0 88 b __set_errno 89END(%(func)s) 90""" 91 92 93# 94# Arm64 assembler templates for each syscall stub 95# 96 97arm64_call = syscall_stub_header + """\ 98 stp x29, x30, [sp, #-16]! 99 mov x29, sp 100 str x8, [sp, #-16]! 101 102 mov x8, %(__NR_name)s 103 svc #0 104 105 ldr x8, [sp], #16 106 ldp x29, x30, [sp], #16 107 108 cmn x0, #(MAX_ERRNO + 1) 109 cneg x0, x0, hi 110 b.hi __set_errno 111 112 ret 113END(%(func)s) 114""" 115 116 117# 118# MIPS assembler templates for each syscall stub 119# 120 121mips_call = "/* " + warning + " */\n" + \ 122""" 123#include <asm/unistd.h> 124 .text 125 .globl %(func)s 126 .align 4 127 .ent %(func)s 128 129%(func)s: 130 .set noreorder 131 .cpload $t9 132 li $v0, %(__NR_name)s 133 syscall 134 bnez $a3, 1f 135 move $a0, $v0 136 j $ra 137 nop 1381: 139 la $t9,__set_errno 140 j $t9 141 nop 142 .set reorder 143 .end %(func)s 144""" 145 146 147# 148# MIPS64 assembler templates for each syscall stub 149# 150 151mips64_call = "/* " + warning + " */\n" + \ 152""" 153#include <asm/unistd.h> 154#include <machine/asm.h> 155#include <machine/regdef.h> 156 .text 157 .globl %(func)s 158 .align 4 159 .ent %(func)s 160 161%(func)s: 162 .set push 163 .set noreorder 164 li v0, %(__NR_name)s 165 syscall 166 bnez a3, 1f 167 move a0, v0 168 j ra 169 nop 1701: 171 move t0, ra 172 bal 2f 173 nop 1742: 175 .cpsetup ra, t1, 2b 176 LA t9,__set_errno 177 .cpreturn 178 j t9 179 move ra, t0 180 .set pop 181 .end %(func)s 182""" 183 184 185# 186# x86 assembler templates for each syscall stub 187# 188 189x86_registers = [ "ebx", "ecx", "edx", "esi", "edi", "ebp" ] 190 191x86_call = """\ 192 movl $%(__NR_name)s, %%eax 193 int $0x80 194 cmpl $-MAX_ERRNO, %%eax 195 jb 1f 196 negl %%eax 197 pushl %%eax 198 call __set_errno 199 addl $4, %%esp 200 orl $-1, %%eax 2011: 202""" 203 204x86_return = """\ 205 ret 206END(%(func)s) 207""" 208 209 210# 211# x86_64 assembler templates for each syscall stub 212# 213 214x86_64_call = """\ 215 movl $%(__NR_name)s, %%eax 216 syscall 217 cmpq $-MAX_ERRNO, %%rax 218 jb 1f 219 negl %%eax 220 movl %%eax, %%edi 221 call __set_errno 222 orq $-1, %%rax 2231: 224 ret 225END(%(func)s) 226""" 227 228 229def param_uses_64bits(param): 230 """Returns True iff a syscall parameter description corresponds 231 to a 64-bit type.""" 232 param = param.strip() 233 # First, check that the param type begins with one of the known 234 # 64-bit types. 235 if not ( \ 236 param.startswith("int64_t") or param.startswith("uint64_t") or \ 237 param.startswith("loff_t") or param.startswith("off64_t") or \ 238 param.startswith("long long") or param.startswith("unsigned long long") or 239 param.startswith("signed long long") ): 240 return False 241 242 # Second, check that there is no pointer type here 243 if param.find("*") >= 0: 244 return False 245 246 # Ok 247 return True 248 249 250def count_arm_param_registers(params): 251 """This function is used to count the number of register used 252 to pass parameters when invoking an ARM system call. 253 This is because the ARM EABI mandates that 64-bit quantities 254 must be passed in an even+odd register pair. So, for example, 255 something like: 256 257 foo(int fd, off64_t pos) 258 259 would actually need 4 registers: 260 r0 -> int 261 r1 -> unused 262 r2-r3 -> pos 263 """ 264 count = 0 265 for param in params: 266 if param_uses_64bits(param): 267 if (count & 1) != 0: 268 count += 1 269 count += 2 270 else: 271 count += 1 272 return count 273 274 275def count_generic_param_registers(params): 276 count = 0 277 for param in params: 278 if param_uses_64bits(param): 279 count += 2 280 else: 281 count += 1 282 return count 283 284 285def count_generic_param_registers64(params): 286 count = 0 287 for param in params: 288 count += 1 289 return count 290 291 292# This lets us support regular system calls like __NR_write and also weird 293# ones like __ARM_NR_cacheflush, where the NR doesn't come at the start. 294def make__NR_name(name): 295 if name.startswith("__"): 296 return name 297 else: 298 return "__NR_%s" % (name) 299 300 301def add_footer(pointer_length, stub, syscall): 302 # Add any aliases for this syscall. 303 aliases = syscall["aliases"] 304 for alias in aliases: 305 stub += function_alias % { "func" : syscall["func"], "alias" : alias } 306 307 # Use hidden visibility for any functions beginning with underscores. 308 if pointer_length == 64 and syscall["func"].startswith("__"): 309 stub += '.hidden _C_LABEL(' + syscall["func"] + ')\n' 310 311 return stub 312 313 314def arm_eabi_genstub(syscall): 315 num_regs = count_arm_param_registers(syscall["params"]) 316 if num_regs > 4: 317 return arm_eabi_call_long % syscall 318 return arm_eabi_call_default % syscall 319 320 321def arm64_genstub(syscall): 322 return arm64_call % syscall 323 324 325def mips_genstub(syscall): 326 return mips_call % syscall 327 328 329def mips64_genstub(syscall): 330 return mips64_call % syscall 331 332 333def x86_genstub(syscall): 334 result = syscall_stub_header % syscall 335 336 numparams = count_generic_param_registers(syscall["params"]) 337 stack_bias = numparams*4 + 4 338 offset = 0 339 mov_result = "" 340 cfi_result = " .cfi_def_cfa_offset %d\n" % (numparams*4) 341 for register in x86_registers[:numparams]: 342 result += " pushl %%%s\n" % register 343 mov_result += " mov %d(%%esp), %%%s\n" % (stack_bias+offset, register) 344 cfi_result += " .cfi_rel_offset %s, %d\n" % (register, offset) 345 offset += 4 346 347 if numparams: 348 result += cfi_result 349 result += mov_result 350 351 result += x86_call % syscall 352 353 for register in reversed(x86_registers[:numparams]): 354 result += " popl %%%s\n" % register 355 356 result += x86_return % syscall 357 return result 358 359 360def x86_genstub_socketcall(syscall): 361 # %ebx <--- Argument 1 - The call id of the needed vectored 362 # syscall (socket, bind, recv, etc) 363 # %ecx <--- Argument 2 - Pointer to the rest of the arguments 364 # from the original function called (socket()) 365 366 result = syscall_stub_header % syscall 367 368 # save the regs we need 369 result += " pushl %ebx\n" 370 result += " pushl %ecx\n" 371 result += " .cfi_def_cfa_offset 8\n" 372 result += " .cfi_rel_offset ebx, 0\n" 373 result += " .cfi_rel_offset ecx, 4\n" 374 stack_bias = 12 375 376 # set the call id (%ebx) 377 result += " mov $%d, %%ebx\n" % syscall["socketcall_id"] 378 379 # set the pointer to the rest of the args into %ecx 380 result += " mov %esp, %ecx\n" 381 result += " addl $%d, %%ecx\n" % (stack_bias) 382 383 # now do the syscall code itself 384 result += x86_call % syscall 385 386 # now restore the saved regs 387 result += " popl %ecx\n" 388 result += " popl %ebx\n" 389 390 # epilog 391 result += x86_return % syscall 392 return result 393 394 395def x86_64_genstub(syscall): 396 result = syscall_stub_header % syscall 397 num_regs = count_generic_param_registers64(syscall["params"]) 398 if (num_regs > 3): 399 # rcx is used as 4th argument. Kernel wants it at r10. 400 result += " movq %rcx, %r10\n" 401 402 result += x86_64_call % syscall 403 return result 404 405 406class State: 407 def __init__(self): 408 self.old_stubs = [] 409 self.new_stubs = [] 410 self.other_files = [] 411 self.syscalls = [] 412 413 414 def process_file(self, input): 415 parser = SysCallsTxtParser() 416 parser.parse_file(input) 417 self.syscalls = parser.syscalls 418 parser = None 419 420 for syscall in self.syscalls: 421 syscall["__NR_name"] = make__NR_name(syscall["name"]) 422 423 if syscall.has_key("arm"): 424 syscall["asm-arm"] = add_footer(32, arm_eabi_genstub(syscall), syscall) 425 426 if syscall.has_key("arm64"): 427 syscall["asm-arm64"] = add_footer(64, arm64_genstub(syscall), syscall) 428 429 if syscall.has_key("x86"): 430 if syscall["socketcall_id"] >= 0: 431 syscall["asm-x86"] = add_footer(32, x86_genstub_socketcall(syscall), syscall) 432 else: 433 syscall["asm-x86"] = add_footer(32, x86_genstub(syscall), syscall) 434 elif syscall["socketcall_id"] >= 0: 435 E("socketcall_id for dispatch syscalls is only supported for x86 in '%s'" % t) 436 return 437 438 if syscall.has_key("mips"): 439 syscall["asm-mips"] = add_footer(32, mips_genstub(syscall), syscall) 440 441 if syscall.has_key("mips64"): 442 syscall["asm-mips64"] = add_footer(64, mips64_genstub(syscall), syscall) 443 444 if syscall.has_key("x86_64"): 445 syscall["asm-x86_64"] = add_footer(64, x86_64_genstub(syscall), syscall) 446 447 # Scan a Linux kernel asm/unistd.h file containing __NR_* constants 448 # and write out equivalent SYS_* constants for glibc source compatibility. 449 def scan_linux_unistd_h(self, fp, path): 450 pattern = re.compile(r'^#define __NR_([a-z]\S+) .*') 451 syscalls = set() # MIPS defines everything three times; work around that. 452 for line in open(path): 453 m = re.search(pattern, line) 454 if m: 455 syscalls.add(m.group(1)) 456 for syscall in sorted(syscalls): 457 fp.write("#define SYS_%s %s\n" % (syscall, make__NR_name(syscall))) 458 459 460 def gen_glibc_syscalls_h(self): 461 # TODO: generate a separate file for each architecture, like glibc's bits/syscall.h. 462 glibc_syscalls_h_path = "include/sys/glibc-syscalls.h" 463 D("generating " + glibc_syscalls_h_path) 464 glibc_fp = create_file(glibc_syscalls_h_path) 465 glibc_fp.write("/* %s */\n" % warning) 466 glibc_fp.write("#ifndef _BIONIC_GLIBC_SYSCALLS_H_\n") 467 glibc_fp.write("#define _BIONIC_GLIBC_SYSCALLS_H_\n") 468 469 glibc_fp.write("#if defined(__aarch64__)\n") 470 self.scan_linux_unistd_h(glibc_fp, bionic_libc_root + "/kernel/uapi/asm-generic/unistd.h") 471 glibc_fp.write("#elif defined(__arm__)\n") 472 self.scan_linux_unistd_h(glibc_fp, bionic_libc_root + "/kernel/uapi/asm-arm/asm/unistd.h") 473 glibc_fp.write("#elif defined(__mips__)\n") 474 self.scan_linux_unistd_h(glibc_fp, bionic_libc_root + "/kernel/uapi/asm-mips/asm/unistd.h") 475 glibc_fp.write("#elif defined(__i386__)\n") 476 self.scan_linux_unistd_h(glibc_fp, bionic_libc_root + "/kernel/uapi/asm-x86/asm/unistd_32.h") 477 glibc_fp.write("#elif defined(__x86_64__)\n") 478 self.scan_linux_unistd_h(glibc_fp, bionic_libc_root + "/kernel/uapi/asm-x86/asm/unistd_64.h") 479 glibc_fp.write("#endif\n") 480 481 glibc_fp.write("#endif /* _BIONIC_GLIBC_SYSCALLS_H_ */\n") 482 glibc_fp.close() 483 self.other_files.append(glibc_syscalls_h_path) 484 485 486 # Write each syscall stub. 487 def gen_syscall_stubs(self): 488 for syscall in self.syscalls: 489 for arch in all_arches: 490 if syscall.has_key("asm-%s" % arch): 491 filename = "arch-%s/syscalls/%s.S" % (arch, syscall["func"]) 492 D2(">>> generating " + filename) 493 fp = create_file(filename) 494 fp.write(syscall["asm-%s" % arch]) 495 fp.close() 496 self.new_stubs.append(filename) 497 498 499 def regenerate(self): 500 D("scanning for existing architecture-specific stub files...") 501 502 bionic_libc_root_len = len(bionic_libc_root) 503 504 for arch in all_arches: 505 arch_path = bionic_libc_root + "arch-" + arch 506 D("scanning " + arch_path) 507 files = glob.glob(arch_path + "/syscalls/*.S") 508 for f in files: 509 self.old_stubs.append(f[bionic_libc_root_len:]) 510 511 D("found %d stub files" % len(self.old_stubs)) 512 513 if not os.path.exists(bionic_temp): 514 D("creating %s..." % bionic_temp) 515 make_dir(bionic_temp) 516 517 D("re-generating stubs and support files...") 518 519 self.gen_glibc_syscalls_h() 520 self.gen_syscall_stubs() 521 522 D("comparing files...") 523 adds = [] 524 edits = [] 525 526 for stub in self.new_stubs + self.other_files: 527 if not os.path.exists(bionic_libc_root + stub): 528 # new file, git add it 529 D("new file: " + stub) 530 adds.append(bionic_libc_root + stub) 531 shutil.copyfile(bionic_temp + stub, bionic_libc_root + stub) 532 533 elif not filecmp.cmp(bionic_temp + stub, bionic_libc_root + stub): 534 D("changed file: " + stub) 535 edits.append(stub) 536 537 deletes = [] 538 for stub in self.old_stubs: 539 if not stub in self.new_stubs: 540 D("deleted file: " + stub) 541 deletes.append(bionic_libc_root + stub) 542 543 if not DRY_RUN: 544 if adds: 545 commands.getoutput("git add " + " ".join(adds)) 546 if deletes: 547 commands.getoutput("git rm " + " ".join(deletes)) 548 if edits: 549 for file in edits: 550 shutil.copyfile(bionic_temp + file, bionic_libc_root + file) 551 commands.getoutput("git add " + " ".join((bionic_libc_root + file) for file in edits)) 552 553 commands.getoutput("git add %s%s" % (bionic_libc_root,"SYSCALLS.TXT")) 554 555 if (not adds) and (not deletes) and (not edits): 556 D("no changes detected!") 557 else: 558 D("ready to go!!") 559 560D_setlevel(1) 561 562state = State() 563state.process_file(bionic_libc_root+"SYSCALLS.TXT") 564state.regenerate() 565