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