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