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