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