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