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