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