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