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