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