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